package arkham.gui;

import javax.imageio.plugins.bmp.BMPImageWriteParam;
import javax.swing.*;

import arkham.jaxb.encounter.ChoiceType;
import arkham.jaxb.encounter.EffectType;
import arkham.jaxb.encounter.EventNode;
import arkham.jaxb.encounter.RollType;
import arkham.net.ClientBase;
import arkham.net.ServerBase;
import arkham.net.messages.*;
import arkham.net.messages.CommandMessage.Command;
import arkham.util.AHLogger;
import arkham.util.Deck;
import arkham.util.ImageUtils;
import arkham.util.Pair;
import arkham.util.XmlTools;
import arkham.data.*;
import arkham.data.Board.Location;
import arkham.data.Board.OtherWorld;
import arkham.data.Board.Path;
import arkham.data.Monster.MonsterPower;
import arkham.data.Globals.*;
import arkham.data.util.AmountParser;
import arkham.data.util.AmountParser.SpecialAmountType;
import arkham.data.util.EventState;
import arkham.data.util.EventState.EventStateType;
import arkham.data.util.EventUtils;
import arkham.data.util.ItemFilter;

import java.util.*;
import java.util.List;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

public class GameScreen extends JFrame implements WindowListener, MessageEndpoint,
    MouseListener, MouseMotionListener, ActionListener, CommandListener
{
    private static final int INFO_PANEL_WIDTH = 160;
    private static final int BOARD_SELECTION_NONE = 0;
    private static final int BOARD_SELECTION_SINGLE = 1;
    private static final int BOARD_SELECTION_TOGGLE = 2;
    private static final int BOARD_SELECTION_MOVEMENT = 3;

    private boolean host, newGame;
    private int width, height;
    private String username, hostusername;
    private ChatManager chatManager;
    private ServerBase server;
    private ClientBase client;
    private int numPlayers, ownerCount, activePlayer;
    private int tempCounter; // this counter can be used for various purposes
    private List<String> players;
    private boolean[] amOwner, actionCompleted;
    private GameData gameData;
    private ReadyChecker readyChecker;

    /* Gui Components */
    private ViewManager viewManager;
    private JPanel infoBar;
    private JPanel chatPanel;

    /* Game state control components */
    private boolean suspendAction, awaitingAction;
    /** The command that is active on the action panel when there is
     * a server command pending. Will be null when during phase-specific
     * routines such as upkeep. */
    private Command actionPanelCommand;
    private Stack<EventState> eventStack;
    private int boardSelectionMode;
    private LocationContainer selectedLocation;
    private Set<String> selectableLocationSet, selectedLocationSet;

    private Object actionLock = new Object();

    private GameScreen(GameData gdata)
    {
        super("Arkham Horror");
        this.gameData = gdata;
        width = 800 + gameData.getExpansionsInPlay() * 250 + INFO_PANEL_WIDTH +
            ViewManager.PHASE_PANEL_WIDTH;
        height = 450 + (gdata.getNumPlayers() > 4 ? 180 : 90) + 100;
        numPlayers = gameData.getNumPlayers();

        this.setSize(new Dimension(width, height));
        this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        this.setIconImage(ImageUtils.loadImage("img/ahicon.png"));
        this.addWindowListener(this);

        /* Set the initial position to middle of screen */
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Dimension screensize = toolkit.getScreenSize();
        this.setLocation((screensize.width - width) / 2, (screensize.height - height - 60) / 2);

        suspendAction = false;
        awaitingAction = false;
        eventStack = new Stack<EventState>();
        boardSelectionMode = BOARD_SELECTION_NONE;
        selectableLocationSet = new HashSet<String>();
        selectedLocationSet = new HashSet<String>();
        // set up a dummy command to avoid null pointer errors
        actionPanelCommand = new Command("", "", -1);
        //TODO: This will be set to true in the case of a loaded game
        newGame = true;
    }

    /**
     * Starts the lobby as a client
     * @param client the underlying network transmission client object
     */
    public GameScreen(ClientBase client, GameData gdata, ChatManager chatman)
    {
        this(gdata);
        host = false;
        username = client.getUsername();
        hostusername = client.getHostUsername();
        this.client = client;
        chatManager = chatman;
        client.setMessageEndpoint(this);
        chatManager.setCommandListener(this);
        gameData.setupGame();
        initUI();
        amOwner = new boolean[gdata.getNumPlayers()];
        actionCompleted = new boolean[gdata.getNumPlayers()];
        activePlayer = -1;
        ownerCount = 0;
        for (int i = 0; i < amOwner.length; i++)
        {
            amOwner[i] = username.equals(gdata.getPlayerName(i));
            if (amOwner[i])
            {
                ownerCount++;
                if (activePlayer == -1)
                {
                    activePlayer = i;
                }
            }
        }
        viewManager.updateTurnOrder(0, activePlayer);
        // inform the server that the game has loaded
        client.sendCommandMessage(new CommandMessage("ready", "gameLoaded"));
    }

    /**
     * Starts the lobby as the host
     * @param server the underlying network transmission server object
     */
    public GameScreen(ServerBase server, GameData gdata, ChatManager chatman, List<String> players)
    {
        this(gdata);
        host = true;
        hostusername = username = server.getUsername();
        this.server = server;
        chatManager = chatman;
        this.players = players;
        server.setMessageEndpoint(this);
        server.stopAccepting();
        chatManager.setCommandListener(this);
        gameData.setupGame();
        initUI();
        readyChecker = new ReadyChecker(players);
        readyChecker.register("gameLoaded", username);
        amOwner = new boolean[gdata.getNumPlayers()];
        actionCompleted = new boolean[gdata.getNumPlayers()];
        activePlayer = -1;
        ownerCount++;
        for (int i = 0; i < amOwner.length; i++)
        {
            amOwner[i] = username.equals(gdata.getPlayerName(i));
            if (amOwner[i])
            {
                ownerCount++;
                if (activePlayer == -1)
                {
                    activePlayer = i;
                }
            }
        }
        advanceGameFlow();
    }

    // for testing only
    public GameScreen(String name, GameData gdata, ChatManager chatman)
    {
        this(gdata);
        host = true;
        hostusername = username = name;
        chatManager = chatman;
        initUI();
    }

    private void initUI()
    {
        initViewManager();
        initChatBox();
        this.setVisible(true);
        initLayout();
        Insets in = this.getInsets();
        this.setSize(new Dimension(width + in.left + in.right, height + in.top + in.bottom));
        initListeners();
    }

    /**
     * The global panel sets on the bottom right of the screen. It contains
     * information pertaining to the entire game: the Ancient One, doom
     * track, terror level, current headline/rumor/environment
     */
    private void initViewManager()
    {
        viewManager = new ViewManager(gameData);
    }

    private void initChatBox()
    {
        chatPanel = new JPanel();
        chatPanel.setLayout(new BoxLayout(chatPanel, BoxLayout.Y_AXIS));
        int chatBoxWidth = width - INFO_PANEL_WIDTH - ViewManager.PHASE_PANEL_WIDTH;
        chatManager.setFixedSize(chatBoxWidth, 100);

        JScrollPane scrollPane = new JScrollPane(chatManager.getChatBox());
        ImageUtils.setFixedSize(scrollPane, chatBoxWidth, 75);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        chatPanel.add(scrollPane);
        chatPanel.add(chatManager.getChatField());
        chatManager.getChatField().requestFocusInWindow();

        chatManager.displayMessage(">> Game started.");
    }

    /**
     * Initializes the layout of the frame.
     */
    private void initLayout()
    {
        Container con = this.getContentPane();
        con.setLayout(new BoxLayout(con, BoxLayout.X_AXIS));
        JPanel leftPanel = new JPanel();
        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));
        viewManager.getMainPanel().setAlignmentX(LEFT_ALIGNMENT);
        chatPanel.setAlignmentX(LEFT_ALIGNMENT);
        leftPanel.add(viewManager.getMainPanel());
        leftPanel.add(Box.createVerticalGlue());
        leftPanel.add(chatPanel);
        JPanel rightPanel = new JPanel();
        viewManager.getInfoPanel().setAlignmentX(RIGHT_ALIGNMENT);
        viewManager.getGlobalPanel().setAlignmentX(RIGHT_ALIGNMENT);
        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
        rightPanel.add(viewManager.getInfoPanel());
        rightPanel.add(Box.createHorizontalGlue());
        rightPanel.add(viewManager.getGlobalPanel());
        rightPanel.setSize(new Dimension(INFO_PANEL_WIDTH, height));
        con.add(viewManager.getPhasePanel());
        con.add(leftPanel);
        con.add(Box.createHorizontalStrut(1));
        con.add(rightPanel);
        con.repaint();
    }

    private void initListeners()
    {
        JPanel mapPanel = viewManager.getMapPanel();
        mapPanel.addMouseListener(this);
        mapPanel.addMouseMotionListener(this);
        for (int i = 0; i < gameData.getNumPlayers(); i++)
        {
            viewManager.getPlayerPanel(i).addMouseListener(this);
            ImageUtils.recursiveAddMouseListener(viewManager.getPlayerPanel(i), this);
        }
        JButton[] buttons = viewManager.getActionButtons();
        for (int i = 0; i < buttons.length; i++)
        {
            buttons[i].addActionListener(this);
        }
    }

    /* ---------------- Mouse event helpers -------------------- */

    /**
     * The player has clicked on the map.
     */
    private void clickedOnMap(int button)
    {
        BoardManager bm = viewManager.getBoardManager();
        JPanel map = viewManager.getMapPanel();
        Point p = map.getMousePosition();
        if (p == null)
        {
            return;
        }
        int mousex = p.x;
        int mousey = p.y;
        Object o = bm.getMouseTarget();

        if (o == null)
        {
            return;
        }
        if (o instanceof Investigator)
        {
            Investigator inv = (Investigator)o;
            if (button == MouseEvent.BUTTON1)
            {
                viewManager.updateInvestigatorDisplay(inv);
            }
            else if (button == MouseEvent.BUTTON3)
            {
                // cycle location containers
                LocationContainer locCont = inv.getLocation();
                if (locCont != null)
                {
                    locCont.cycleInvestigators();
                    bm.updateMap();
                    bm.mouseAt(mousex, mousey);
                    map.repaint();
                }
            }
        }
        else if (o instanceof Monster)
        {
            Monster mon = (Monster)o;
            if (button == MouseEvent.BUTTON1)
            {
                viewManager.getInfoDisplay().displayMonster(mon);
            }
            else if (button == MouseEvent.BUTTON3)
            {
                // cycle location containers
                LocationContainer locCont = mon.getLocation();
                if (locCont != null)
                {
                    locCont.cycleMonsters();
                    bm.updateMap();
                    bm.mouseAt(mousex, mousey);
                    map.repaint();
                }
            }
        }
        else if (o instanceof GateTile)
        {

        }
        else if (o instanceof Location)
        {
            Location loc = (Location)o;
            if (button == MouseEvent.BUTTON1)
            {
                viewManager.getInfoDisplay().displayLocation(loc);
            }
        }
        if (boardSelectionMode == BOARD_SELECTION_TOGGLE)
        {
            /* To handle board selection toggle */
            Location loc = viewManager.getBoardManager().getLocationAt(mousex, mousey);
            if (loc != null && selectableLocationSet.contains(loc.getName()));
            {
                if (selectedLocationSet.contains(loc.getName()))
                {
                    selectedLocationSet.remove(loc.getName());
                    viewManager.getBoardManager().clearLocationOverlay(loc.getName());
                }
                else
                {
                    selectedLocationSet.add(loc.getName());
                    viewManager.getBoardManager().setLocationOverlay(loc.getName(),
                            BoardManager.ICON_VORTEX);
                }
            }
        }
        else if (boardSelectionMode == BOARD_SELECTION_MOVEMENT)
        {
            Location loc = viewManager.getBoardManager().getLocationAt(mousex, mousey);
            Investigator inv = gameData.getCurrentInvestigator();
            if (loc != null && !loc.equals(selectedLocation) && !inv.areMovementPointsLocked() &&
                    inv.getLocation() instanceof Location)
            {
                int dist = gameData.computeDistance((Location)inv.getLocation(), loc);
                if (dist <= inv.getMovementPoints())
                {
                    Path path = gameData.computePath((Location)inv.getLocation(), loc,
                            inv.getMovementPoints(), inv.hasAvoidedMonsters());
                    if (path == null)
                    {
                        return;
                    }
                    bm.setDisplayedPath(path);
                    bm.updateOverlay();
                    map.repaint();
                    selectedLocation = loc;
                    if (inv.getLocation().getNumMonsters() > 0 && !inv.hasAvoidedMonsters())
                    {
                        viewManager.updateActionPanelText("Movement", "There are monsters in your " +
                                "location! Press OK to fight or evade them.");
                    }
                    else if (selectedLocation.equals(inv.getLocation()))
                    {
                        viewManager.updateActionPanelText("Movement", "Press OK to end movement.");
                    }
                    else if (path.getBlockedNodes() == 0)
                    {
                        viewManager.updateActionPanelText("Movement", "Press OK to move to " +
                                selectedLocation.getName());
                    }
                    else
                    {
                        viewManager.updateActionPanelText("Movement", "There are monsters in your " +
                                "way! Press OK to move to " + path.lastUnblockedLocation().getName());
                    }
                }
            }
        }
    }

    /**
     * The player has clicked on one of the player panels
     * @param index the player panel clicked on
     */
    private void clickedOnPlayer(int index, int button)
    {
        int oldActivePlayer = activePlayer;
        if (button == MouseEvent.BUTTON1)
        {
            if (amOwner[index])
            {
                activePlayer = index;
                viewManager.updateTurnOrder(gameData.getCurrentPlayer(), activePlayer);
            }
            if (activePlayer != oldActivePlayer)
            {
                switchControl(activePlayer);
            }
            Investigator inv = gameData.getInvestigator(index);
            if (inv == null)
            {
                return;
            }
            else
            {
                viewManager.updateInvestigatorDisplay(inv);
            }
        }
    }

    /**
     * Cycles the active player to the next active player
     */
    private void nextActivePlayer()
    {
        int origPlayer = activePlayer;
        if (ownerCount <= 1)
        {
            return;
        }
        if (amOwner[gameData.getCurrentPlayer() % gameData.getNumPlayers()])
        {
            activePlayer = gameData.getCurrentPlayer() % gameData.getNumPlayers();
        }
        else
        {
            for (int i = 1; i < gameData.getNumPlayers(); i++)
            {
                if (amOwner[(gameData.getCurrentPlayer() + i) % gameData.getNumPlayers()])
                {
                    activePlayer = (gameData.getCurrentPlayer() + i) % gameData.getNumPlayers();
                    break;
                }
            }
        }
        if (activePlayer != origPlayer)
        {
            switchControl(activePlayer);
        }
    }

    /**
     * Switches control to a different investigator in the case the player
     * is playing multiple investigators
     * @param player the player to switch control to
     */
    private void switchControl(int player)
    {
        if (gameData.isStarted())
        {

        }
        else
        {
            switch (gameData.getGameSetupState())
            {
            case SELECT_INVESTIGATORS:
                if (amOwner[gameData.getCurrentPlayer()] && gameData.getCurrentInvestigator() != null)
                {
                    viewManager.updateForSliderAdjustment(gameData.getCurrentInvestigator(), true);
                }
                else
                {
                    updateActionForInvSelection();
                }
                break;
            }
        }
    }
    /* ------------------ Action Listener code ----------------- */


    /**
     * Updates the viewmanager's action panel to represent the current
     * investigator selection.
     */
    private void updateActionForInvSelection()
    {
        if (gameData.isDraftMode())
        {
            viewManager.updateForInvSelection(gameData.getInvestigatorDraftPool(),
                    activePlayer == gameData.getCurrentPlayer());
        }
        else
        {
            List<Investigator> invlist = new ArrayList<Investigator>();
            int mult = gameData.getInvestigatorSelectionValue();
            for (int i = 0; i < mult; i++)
            {
                invlist.add(gameData.getInvestigatorDraftPool().get(mult * activePlayer + i));
            }
            Collections.sort(invlist, NamedEntity.NameComparator);
            viewManager.updateForInvSelection(invlist, activePlayer == gameData.getCurrentPlayer());
        }
    }

    /**
     * Updates the action panel for upkeep actions
     * @param activePlayer set to true if this player is the active player
     */
    private void updateActionForUpkeep(boolean activePlayer)
    {
        if (activePlayer)
        {
            if (!host)
            {
                /* resume action if you are the client */
                synchronized(actionLock)
                {
                    suspendAction = false;
                }
            }
            Investigator inv = gameData.getCurrentInvestigator();
            viewManager.updateForUpkeep("Upkeep", "Use the skill sliders to adjust your " +
            		"skills and press OK when ready.", inv);
            awaitingAction = true;
            actionPanelCommand = null;
        }
        else
        {
            viewManager.updateForNoAction("Upkeep", "Please wait while " +
                    gameData.getCurrentPlayerName() + " performs upkeep actions.");
        }
    }

    /**
     * Updates the action panel for upkeep effect actions
     * @param activePlayer set to true if this player is the active player
     */
    private void updateActionForUpkeepEffects(boolean activePlayer)
    {
        if (activePlayer)
        {
            if (!host)
            {
                /* resume action if you are the client */
                synchronized(actionLock)
                {
                    suspendAction = false;
                }
            }
            Investigator inv = gameData.getCurrentInvestigator();
            List<UsableEffect> ueffList = inv.searchUsableEffects(UsablePhase.UPKEEP);
            GameData.removeUnavailableEffects(inv, ueffList);
            viewManager.updateForUsableEffects("Upkeep", "Do you wish to use any cards " +
            		"or abilities during upkeep?", ueffList);
            awaitingAction = true;
            actionPanelCommand = null;
        }
        else
        {
            viewManager.updateForNoAction("Upkeep", "Please wait while " +
                    gameData.getCurrentPlayerName() + " performs upkeep actions.");
        }
    }

    /**
     * Sets up the action panel for user input during movement
     * @param activePlayer set to true if this player is the active player
     */
    private void updateActionForMovement(boolean activePlayer)
    {
        if (!activePlayer)
        {
            viewManager.updateForNoAction("Movement", "Please wait while " +
                    gameData.getCurrentPlayerName() + " completes his/her movement.");
            boardSelectionMode = BOARD_SELECTION_NONE;
            return;
        }
        if (!host)
        {
            /* resume action if you are the client */
            synchronized(actionLock)
            {
                suspendAction = false;
            }
        }
        Investigator inv = gameData.getCurrentInvestigator();
        String desc = "Click on the map to set a destination. Press OK to end movement.";

        List<UsableEffect> uefflist = new ArrayList<UsableEffect>();
        if (inv.areMovementEffectsLocked())
        {
            if (inv.isDelayed())
            {
                desc = "You are delayed and may not use effects. Press OK to end movement.";
            }
        }
        else
        {
            inv.searchUsableEffects(uefflist, UsablePhase.MOVEMENT);
            inv.searchUsableEffects(uefflist, UsablePhase.ANY);
            if (inv.getLocation().isArkhamLocation())
            {
                inv.searchUsableEffects(uefflist, UsablePhase.MOVEMENT_ARKHAM);
                if (inv.getLocation().getNumMonsters() > 0 && !inv.hasAvoidedMonsters())
                {
                    desc = "There are monsters in your location. Press OK when ready to " +
                    		"deal with them.";
                }
            }
            else
            {
                inv.searchUsableEffects(uefflist, UsablePhase.MOVEMENT_OW);
                desc = "You may use effects and/or trade. Press OK to advance to the next space.";
            }
        }
        GameData.removeUnavailableEffects(inv, uefflist);
        List<String> tradelist = new ArrayList<String>();
        for (Investigator friend : inv.getLocation().getInvestigatorList())
        {
            if (!friend.getName().equals(inv.getName()))
            {
                tradelist.add(friend.getName());
            }
        }
        viewManager.updateForMovement("Movement", desc, inv, uefflist, tradelist);
        awaitingAction = true;
        actionPanelCommand = null;
        selectedLocation = null;
        boardSelectionMode = BOARD_SELECTION_MOVEMENT;
    }

    /**
     * Sets up the action panel for user input during trading
     * @param activePlayer set to true if this player is the active player
     */
    private void updateActionForTrade(boolean activePlayer)
    {
        boardSelectionMode = BOARD_SELECTION_NONE;
        if (!activePlayer)
        {
            viewManager.updateForNoAction("Trade", "Please wait while " +
                    gameData.getCurrentPlayerName() + " performs his/her trade.");
            return;
        }
        if (!host)
        {
            /* resume action if you are the client */
            synchronized(actionLock)
            {
                suspendAction = false;
            }
        }
        Investigator inv = gameData.getCurrentInvestigator();
        Investigator tinv = gameData.getTradeRecipient();
        viewManager.updateForTrade("Trade", "Check the items that you wish to be traded, " +
        		"then press OK.", inv, tinv);
        awaitingAction = true;
        actionPanelCommand = null;
        selectedLocation = null;
    }

    /**
     * Sets up the action panel for user input during trade acceptance
     * @param activePlayer set to true if this player is the active player
     */
    private void updateActionForTradeAccept(boolean activePlayer)
    {
        boardSelectionMode = BOARD_SELECTION_NONE;
        if (!activePlayer)
        {
            viewManager.updateForNoAction("Trade", "Please wait while " +
                    gameData.getCurrentPlayerName() + " chooses whether to accept the trade.");
            return;
        }
        if (!host)
        {
            /* resume action if you are the client */
            synchronized(actionLock)
            {
                suspendAction = false;
            }
        }
        List<InvestigatorCard> list1 = new ArrayList<InvestigatorCard>();
        List<InvestigatorCard> list2 = new ArrayList<InvestigatorCard>();
        Investigator inv = gameData.getCurrentInvestigator();
        Investigator tinv = gameData.getTradeRecipient();
        boolean res = gameData.validateTrade(gameData.getTradeDetails(), inv, tinv, list1, list2);
        if (!res)
        {
            AHLogger.error("updateForTradeAccept", "Error parsing server provided data.");
            return;
        }
        viewManager.updateForTradeAccept("Trade", inv.getName() + " is trading the following " +
                "items with you. Press OK to accept or Cancel to decline.", tinv, inv, list2, list1);
        awaitingAction = true;
        actionPanelCommand = null;
    }

    /**
     * Sets up the action panel for user input during start of combat to choose
     * which monster to engage
     * @param activePlayer set to true if this player is the active player
     */
    private void updateActionForStartCombat(boolean activePlayer)
    {
        boardSelectionMode = BOARD_SELECTION_NONE;
        selectedLocation = null;
        if (!activePlayer)
        {
            viewManager.updateForNoAction("Combat", "Please wait while " +
                    gameData.getCurrentPlayerName() + " engages in combat.");
            return;
        }
        if (!host)
        {
            /* resume action if you are the client */
            synchronized(actionLock)
            {
                suspendAction = false;
            }
        }
        viewManager.updateForStartCombat("Select Monster", "Select the monster you wish to engage" +
        		" and press OK.", gameData.getCombatFrame());
        awaitingAction = true;
        actionPanelCommand = null;
    }

    /**
     * Sets up the action panel for user input
     * @param cmd the command to act upon
     * @param details the details of the command
     * @param the player carrying out the action
     */
    private void setupActionPanel(String cmd, String details, int player)
    {
        actionPanelCommand = new Command(cmd, details, player);
        if (cmd.equals("!Invcardchoice"))
        {
            // prepare action panel for item selection
            String[] tokens = details.split(",");
            int picks = XmlTools.parseInt(tokens[0]);
            List<InvestigatorCard> list = new ArrayList<InvestigatorCard>();
            for (int i = 1; i < tokens.length; i++)
            {
                InvestigatorCard invcard = gameData.lookupCardArray(XmlTools.parseInt(tokens[i]));
                list.add(invcard);
            }
            viewManager.updateForCardSelection("Choose cards", "Use the up and down " +
            		"arrows to rearrange the cards. Only the top " +
                    picks + " cards will be retained.", list, picks);
            awaitingAction = true;
        }
        else if (cmd.equals("!Losecardchoice"))
        {
            String[] tokens = details.split(",");
            int picks = XmlTools.parseInt(tokens[0]);
            List<InvestigatorCard> list = new ArrayList<InvestigatorCard>();
            for (int i = 1; i < tokens.length; i++)
            {
                InvestigatorCard invcard = gameData.lookupCardArray(XmlTools.parseInt(tokens[i]));
                list.add(invcard);
            }
            viewManager.updateForCardSelection("Choose discards", "Use the up and down " +
                    "arrows to rearrange the cards. The top " +
                    picks + " cards will be DISCARDED in that order.", list, picks);
            awaitingAction = true;
        }
        else if (cmd.equals("!Scrounge"))
        {
            String[] tokens = details.split(",");
            int picks = XmlTools.parseInt(tokens[1]);
            tempCounter = picks;
            String desc = "Scrounging from the " + tokens[0] + " deck. " + picks +
                " choices remaining.";
            if (tokens.length > 2)
            {
                DefaultListModel model = viewManager.getCardChoiceListModel();
                model.clear();
                for (int i = 2; i < tokens.length; i++)
                {
                    model.addElement(gameData.lookupCardDatabase(tokens[i]));
                }
                viewManager.updateActionPanelText("Scrounge", desc);
            }
            else
            {
                viewManager.updateForCardChoice("Scrounge", desc,
                        new String[] {"Top", "Bottom", "Peek"},
                        new String[] {"Scrounge-Top", "Scrounge-Bottom", "Scrounge-Peek"},
                        this, picks);
            }
            awaitingAction = true;
        }
        else if (cmd.equals("!PlayerChoice"))
        {
            ArrayList<String> list = new ArrayList<String>();
            list.add("Yes");
            list.add("No");
            viewManager.updateForTextChoices("Encounter Choice", details, list);
            awaitingAction = true;
        }
        else if (cmd.equals("!SurgePlacement"))
        {
            String[] tokens = details.split(",");

            int numMonsters = XmlTools.parseInt(tokens[1]);
            int openGates = gameData.getNumOpenGates();

            String desc = "Monster surge on " + tokens[0] + " with " + tokens[1] +
                " monsters. " + (numMonsters / openGates) + " monsters will be placed" +
                " on each gate, 1 more on the surging gate. Choose " +
                (numMonsters % openGates - 1) + " more gates to place monsters on.";

            viewManager.updateForConfirmAction("Choose Surge Gates", desc);
            boardSelectionMode = BOARD_SELECTION_TOGGLE;
            selectableLocationSet.clear();
            List<Location> list = gameData.getOpenGateLocations();
            for (Location loc : list)
            {
                if (!loc.getName().equals(tokens[0]))
                {
                    selectableLocationSet.add(loc.getName());
                }
            }
            awaitingAction = true;
        }
        else if (cmd.equals("!AllocateClue"))
        {
            Location loc = gameData.getLocation(details);
            List<Investigator> invlist = loc.getInvestigatorList();
            List<String> stringList = new ArrayList<String>();
            for (Investigator inv : invlist)
            {
                stringList.add(inv.getName());
            }
            viewManager.updateForTextChoices("Allocate Clue", "A clue appears at " +
                    loc.getName() +	". Please decide which investigator gets it.", stringList);
            awaitingAction = true;
        }
        else if (cmd.equals("!HealTarget"))
        {
            String[] tokens = details.split(",");
            List<String> stringList = new ArrayList<String>();
            for (String token : tokens)
            {
                stringList.add(token);
            }
            viewManager.updateForTextChoices("Heal Target", "Who do you wish to heal?", stringList);
            awaitingAction = true;
        }
        else if (cmd.equals("Allocate Monster"))
        {
            StringTokenizer st = new StringTokenizer(details);
            int monid = XmlTools.parseInt(st.nextToken());
            List<String> stringList = new ArrayList<String>();
            while (st.hasMoreTokens())
            {
                stringList.add(st.nextToken());
            }
            Monster mon = gameData.getMonsterById(monid);
            viewManager.updateForTextChoices("Allocate Monster", "Please decide which of " +
                    "these investigators the " + mon.getName() + " moves to.", stringList);
            awaitingAction = true;
        }
        else if (cmd.equals("!PreventDamage"))
        {
            String[] tokens = details.split(",");
            Investigator inv = gameData.getInvestigator(player);
            if (tokens[0].equals("Stamina"))
            {
                List<ModEffect> stamProtList = inv.searchModifiers("Prevent Damage", "Stamina");
                GameData.removeUnavailableEffects(inv, stamProtList);
                viewManager.updateForModEffects("Prevent Damage", "Do you wish to use any of " +
                        "the following to avoid losing " + tokens[1] + " Stamina?", stamProtList);
                awaitingAction = true;
            }
            else if (tokens[0].equals("Sanity"))
            {
                List<ModEffect> sanProtList = inv.searchModifiers("Prevent Damage", "Sanity");
                GameData.removeUnavailableEffects(inv, sanProtList);
                viewManager.updateForModEffects("Prevent Damage", "Do you wish to use any of " +
                        "the following to avoid losing " + tokens[1] + " Sanity?", sanProtList);
                awaitingAction = true;
            }
        }
        else if (cmd.equals("!RedrawEncounter"))
        {
            String[] tokens = details.split(",");
            Investigator inv = gameData.getInvestigator(player);
            List<ModEffect> modList = inv.searchModifiers("Special", "Redraw Location Card");
            GameData.removeUnavailableEffects(inv, modList);
            viewManager.updateForModEffects("Redraw Encounter", "Do you wish to use any of " +
                    "the following to redraw the encounter at " + tokens[0] + "?", modList);
            awaitingAction = true;
        }
        else if (cmd.equals("!BeforeCheck"))
        {
            String[] tokens = details.split(",");
            String skillStr = tokens[0];
            InvestigatorSkill priSkill = InvestigatorSkill.getPrimarySkill(skillStr);
            Investigator inv = gameData.getInvestigator(player);
            List<ModEffect> modlist = new ArrayList<ModEffect>();
            if (InvestigatorSkill.isSecondarySkill(skillStr))
            {
                /* Also check the secondary skill name in the case of
                 * secondary skills
                 */
                modlist.addAll(inv.searchModifiers("Before " + skillStr + " Check"));
            }
            modlist.addAll(inv.searchModifiers("Before " + priSkill + " Check"));
            modlist.addAll(inv.searchModifiers("Before Skill Check"));
            GameData.removeUnavailableEffects(inv, modlist);
            viewManager.updateForModEffects("Before " + skillStr + " Check",
                    "Do you wish to use any effects before making the " + skillStr + "(" +
                    		tokens[1] + ") check?", modlist);
            awaitingAction = true;
        }
        else if (cmd.equals("!EvadeMonster"))
        {
            int id = XmlTools.parseInt(details);
            Monster mon = gameData.getMonsterById(id);
            Investigator inv = gameData.getInvestigator(player);
            List<ModEffect> modlist = inv.searchModifiers("Evade Monster");
            GameData.removeUnavailableEffects(inv, modlist);
            viewManager.updateForModEffects("Evade Monster", "Do you wish to use any effects " +
            		"to evade the " + mon.getName() + "?", modlist);
            awaitingAction = true;
        }
        else if (cmd.equals("!Encounter"))
        {
            String[] tokens = details.split(",");
            Investigator inv = gameData.getInvestigator(player);
            int numEncounters = XmlTools.parseInt(tokens[1]);
            /* If there is only one encounter, just display the Ok button and set as current
             * encounter. If there are multiple encounters, prompt the user with a choice
             */
            if (numEncounters == 1)
            {
                EventNode en = gameData.drawLocationCard(tokens[0], tokens[2]);
                viewManager.getInfoDisplay().displayEncounter(tokens[0], en.getDescription());
                gameData.setCurrentEvent(en);
                viewManager.updateForConfirmAction("Arkham Encounter", inv.getName() + " has an" +
                        " encounter at the " + tokens[0] + ". Press OK to continue.");
                awaitingAction = true;
            }
            else
            {
                List<EventNode> encdescs = new ArrayList<EventNode>();
                for (int i = 2; i < tokens.length; i++)
                {
                    EventNode en = gameData.drawLocationCard(tokens[0], tokens[i]);
                    encdescs.add(en);
                    viewManager.updateForEncounterSelection("Arkham Encounter", "Choose which of " +
                            "the following you wish to encounter, then press OK.", encdescs);
                }
                awaitingAction = true;
            }
        }
        else if (cmd.equals("!OWEncounter"))
        {
            String[] tokens = details.split(",");
            Investigator inv = gameData.getInvestigator(player);
            int numEncounters = XmlTools.parseInt(tokens[1]);
            /* If there is only one encounter, just display the Ok button and set as current
             * encounter. If there are multiple encounters, prompt the user with a choice
             */
            if (numEncounters == 1)
            {
                GateCard card = gameData.getGateCardByName(tokens[2]);
                viewManager.getInfoDisplay().displayGateCard(card, tokens[0]);
                gameData.setCurrentEvent(card.getOtherWorldEncounter(tokens[0]));
                viewManager.updateForConfirmAction("Other World Encounter", inv.getName() + " has an" +
                        " encounter in " + tokens[0] + ". Press OK to continue.");
                awaitingAction = true;
            }
            else
            {
                List<GateCard> cards = new ArrayList<GateCard>();
                for (int i = 2; i < tokens.length; i++)
                {
                    GateCard card = gameData.getGateCardByName(tokens[i]);
                    cards.add(card);
                    viewManager.updateForOWEncounterSelection("Other World Encounter",
                            "Choose which of the following you wish to encounter, " +
                            "then press OK.", cards, tokens[0]);
                }
                awaitingAction = true;
            }
        }
        else
        {
            AHLogger.warning("setupActionPanel", "Unrecognized command: " + cmd + "," + details);
        }
        if (!host)
        {
            /* resume action if you are the client */
            synchronized(actionLock)
            {
                suspendAction = false;
            }
        }
    }

    /**
     * Sets up the roll panel to display dice results
     * @param details the detail string, containing the roll type and die rolls
     * @param player the index of the investigator making the roll
     */
    private void setupRollPanel(String details, int player)
    {
        actionPanelCommand = new Command("*Roll", details, player);
        String[] tokens = details.split(",");
        InvestigatorSkill priSkill = InvestigatorSkill.getPrimarySkill(tokens[0]);
        Investigator inv = gameData.getInvestigator(player);
        List<Integer> diceList = new ArrayList<Integer>();
        List<ModEffect> modlist = new ArrayList<ModEffect>();
        String rollString = "";
        for (int i = 3; i < tokens.length; i++)
        {
            if (!XmlTools.isNumeric(tokens[i]))
            {
                AHLogger.error("SetupRollPanel", "Non-numeric die rolls found.");
                continue;
            }
            diceList.add(new Integer(XmlTools.parseInt(tokens[i])));
        }
        if (tokens[0].equals("Stamina") || tokens[0].equals("Sanity"))
        {
            /* Stamina and sanity rolls do not use clue tokens,
               also there is no difficulty specified */
            rollString = tokens[0] + " roll";
            if (amOwner[player])
            {
                viewManager.updateForRoll(rollString, "Press OK to continue.", diceList,
                        modlist, false);
                awaitingAction = true;
            }
            else
            {
                viewManager.updateForRollViewing(rollString, "Please wait as " +
                        inv.getName() + " makes a " + rollString + ".", diceList);
            }

        }
        else
        {
            if (!tokens[1].startsWith("-"))
            {
                tokens[1] = "+" + tokens[1];
            }
            if (tokens[2].equals("1"))
            {
                rollString = tokens[0] + "(" + tokens[1] + ") check";
            }
            else
            {
                rollString = tokens[0] + "(" + tokens[1] + ")[" + tokens[2] + "] check";
            }
            /* Populate mod effects list */
            if (InvestigatorSkill.isSecondarySkill(tokens[0]))
            {
                /* Also check the secondary skill name in the case of
                 * secondary skills
                 */
                modlist.addAll(inv.searchModifiers("During " + tokens[0] + " Check"));
            }
            modlist.addAll(inv.searchModifiers("During " + priSkill + " Check"));
            modlist.addAll(inv.searchModifiers("During Skill Check"));
            GameData.removeUnavailableEffects(inv, modlist);
            if (amOwner[player])
            {
                if (diceList.size() > 0)
                {
                    viewManager.updateForRoll(rollString, "You may use clues and effects (if any), " +
                        "then press OK to continue.", diceList, modlist, inv.getClues() > 0);
                }
                else
                {
                    viewManager.updateForRoll(rollString, "Your skill is not high enough to " +
                    		"roll any dice. Use clues or effects (if any) or press OK" +
                    		" to continue.", diceList, modlist, inv.getClues() > 0);
                }
                awaitingAction = true;
            }
            else
            {
                viewManager.updateForRollViewing(rollString, "Please wait as " +
                        inv.getName() + " makes a " + rollString + ".", diceList);
            }
        }
        if (amOwner[player] && !host)
        {
            /* resume action if you are the client */
            synchronized(actionLock)
            {
                suspendAction = false;
            }
        }
    }

    /**
     * Sets up the combat panel, which allows the player to make decisions as
     * to whether to evade, and what weapons to be used in combat.
     * @param details the details string
     * @param player the index of the investigator facing the combat
     */
    private void setupCombatPanel(String details, int player)
    {
        actionPanelCommand = new Command("*Combat", details, player);
        String[] tokens = details.split(",");
        Monster mon = gameData.getMonsterById(Integer.parseInt(tokens[0]));
        int rating = XmlTools.parseInt(tokens[1]);
        int toughness = XmlTools.parseInt(tokens[2]);
        int physRes = XmlTools.parseInt(tokens[3]);
        int magRes = XmlTools.parseInt(tokens[4]);
        int handsLeft = XmlTools.parseInt(tokens[5]);
        boolean evadeOption = Boolean.parseBoolean(tokens[6]);
        Investigator inv = gameData.getInvestigator(player);

        List<Object> equippedList = new ArrayList<Object>();
        if (tokens.length >= 8)
        {
            EffectSource effsrc = gameData.lookupEffectSource(tokens[7]);
            equippedList.add(effsrc);
            if (tokens.length >= 9)
            {
                effsrc = gameData.lookupEffectSource(tokens[8]);
                equippedList.add(effsrc);
            }
        }
        else if (handsLeft < 2)
        {
            equippedList.add(new String("Failed spell"));
        }
        List<EffectSource> weaponList = inv.getPermanentWeapons();
        /* TODO: Sort weapons and pre-select best ones */
        // sortWeapons(weaponList, physRes, magRes);
        List<ModEffect> modlist = inv.searchModifiers("Weapon");
        inv.searchModifiers("Combat Spell", modlist);
        GameData.removeUnavailableEffects(inv, modlist);
        if (amOwner[player])
        {
            viewManager.updateForCombat("Combat", "Select and use weapons and/or spells. " +
            		"Press OK when ready to start combat.", mon, equippedList,
            		handsLeft, weaponList, 0, modlist, evadeOption);
            awaitingAction = true;
        }
        else
        {
            viewManager.updateForNoAction("Combat", "Please wait while " + inv.getName() + " is " +
            		"fighting the " + mon.getName());
        }
        if (amOwner[player] && !host)
        {
            /* resume action if you are the client */
            synchronized(actionLock)
            {
                suspendAction = false;
            }
        }
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        synchronized(actionLock)
        {
            if (suspendAction)
            {
                return;
            }
        }
        /* Place action commands here with no game flow implications */
        if (e.getActionCommand().equals("Scrounge-Peek"))
        {
            commandReceived(new String[] {"use", "scrounge"});
            return;
        }
        /* Place action commands here with game flow implications */
        boolean anyAction = false;
        if (awaitingAction && actionPanelCommand != null)
        {
            /* This section deals with actions as a response to the event stack */
            String cmd = actionPanelCommand.cmd;
            String details = actionPanelCommand.details;
            if ((cmd.equals("!Invcardchoice") || cmd.equals("!Losecardchoice"))
                    && e.getActionCommand().equals("actionOk"))
            {
                StringBuffer sb = new StringBuffer();
                int m = XmlTools.parseInt(details.substring(0, details.indexOf(',')));
                for (int i = 0; i < m; i++)
                {
                    InvestigatorCard invcard = (InvestigatorCard)viewManager
                        .getInvCardListModel().getElementAt(i);
                    if (i != 0)
                    {
                        sb.append(",");
                    }
                    sb.append(invcard.getId());
                }
                awaitingAction = false;
                if (host)
                {
                    serverCommandHandler(cmd, sb.toString(), username);
                }
                else
                {
                    sendUIActionAsClient(cmd, sb.toString());
                }
                anyAction = true;
            }
            else if (cmd.equals("!Scrounge"))
            {
                if (e.getActionCommand().startsWith("Scrounge-"))
                {
                    tempCounter--;
                    if (tempCounter <= 0)
                    {
                        awaitingAction = false;
                    }
                    if (host)
                    {
                        serverCommandHandler(cmd, e.getActionCommand(), username);
                    }
                    else
                    {
                        sendUIActionAsClient(cmd, e.getActionCommand());
                    }
                    anyAction = true;
                }
            } // end of !scrounge
            else if (cmd.equals("!PreventDamage") || cmd.equals("!BeforeCheck") ||
                    cmd.equals("!EvadeMonster") || cmd.equals("!RedrawEncounter")
                    || cmd.equals("!RedrawOWEncounter"))
            {
                if (e.getActionCommand().equals("actionOk"))
                {
                    /* Player uses a mod effect */
                    String s = "";
                    Object o = viewManager.getActionList().getSelectedItem();
                    if (o != null && o.equals("None"))
                    {
                        s = "None";
                    }
                    else if (o != null && o instanceof ModEffect)
                    {
                        ModEffect mod = (ModEffect)o;
                        Investigator inv = gameData.getCurrentInvestigator();
                        s = mod.getIdentifier();
                        if (mod.getUsage().equals(EffectUsage.LIMITED_MULTI))
                        {
                            // For limited items, ask user how many times they wish to use it
                            int maxUses = mod.getSource().getLimit() - mod.getSource().getNumCounters();
                            int uses = -1;
                            String valStr = JOptionPane.showInputDialog(this, "How many times " +
                                "would you like to use " + mod.getSource().getName() +
                                "? (Max: " + maxUses + ")");
                            if (valStr == null)
                            {
                                return;
                            }
                            if (!XmlTools.isNumeric(valStr))
                            {
                                JOptionPane.showMessageDialog(this, "Please input a valid number",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            uses = XmlTools.parseInt(valStr);
                            if (uses < 1 || uses > maxUses)
                            {
                                JOptionPane.showMessageDialog(this, "Please input a valid number",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            else
                            {
                                s += "," + uses;
                            }
                        } // end of if 'limited usage'
                        else if (mod.getUsage().equals(EffectUsage.CAST_AND_EXHAUST) ||
                                mod.getUsage().equals(EffectUsage.CAST_AND_DISCARD) &&
                                mod.getSource() instanceof Spell)
                        {
                            // For spells, check if the investigator has enough sanity to cast
                            Spell spell = (Spell)mod.getSource();
                            if (inv.getCurrentSanity() < spell.getSanityCost())
                            {
                                JOptionPane.showMessageDialog(this, "Not enough Sanity to cast " +
                                        spell.getName(), "Error",
                                        JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            else if (inv.getCurrentSanity() == spell.getSanityCost())
                            {
                                int res = JOptionPane.showConfirmDialog(this,
                                        "Casting this spell will cause you to go insane! " +
                                        "Proceed anyway?", "Confirm selection",
                                        JOptionPane.YES_NO_OPTION);
                                if (res != JOptionPane.YES_OPTION)
                                {
                                    return;
                                }
                            }
                        }
                    }
                    awaitingAction = false;
                    if (host)
                    {
                        serverCommandHandler(cmd, s, username);
                    }
                    else
                    {
                        sendUIActionAsClient(cmd, s);
                    }
                    anyAction = true;
                }
            } // end of !PreventDamage and other mod effects
            else if (cmd.equals("!SurgePlacement") && e.getActionCommand().equals("actionOk"))
            {
                String[] tokens = details.split(",");
                int numMonsters = XmlTools.parseInt(tokens[1]);
                int rem = numMonsters % gameData.getNumOpenGates() - 1;
                if (selectedLocationSet.size() != numMonsters)
                {
                    JOptionPane.showMessageDialog(this, "Please select " +
                        rem + " surging gates.", "Error", JOptionPane.ERROR_MESSAGE);
                    return;
                }

                boardSelectionMode = BOARD_SELECTION_NONE;
                StringBuffer sb = new StringBuffer();
                for (String locName : selectedLocationSet)
                {
                    if (sb.length() != 0) sb.append(",");
                    sb.append(locName);
                }
                selectedLocationSet.clear();
                viewManager.getBoardManager().clearAllLocationOverlays();
                awaitingAction = false;
                if (host)
                {
                    serverCommandHandler(cmd, sb.toString(), username);
                }
                else
                {
                    sendUIActionAsClient(cmd, sb.toString());
                }
                anyAction = true;
            } // end of !SurgePlacement
            else if (cmd.equals("!AllocateClue") && e.getActionCommand().equals("actionOk"))
            {
                Object o = viewManager.getActionList().getSelectedItem();
                if (o != null && o instanceof String)
                {
                    awaitingAction = false;
                    String s = (String)o;
                    if (host)
                    {
                        serverCommandHandler(cmd, s, username);
                    }
                    else
                    {
                        sendUIActionAsClient(cmd, s);
                    }
                    anyAction = true;
                }
            } // end of !AllocateClue
            else if (cmd.equals("Allocate Monster") && e.getActionCommand().equals("actionOk"))
            {
                Object o = viewManager.getActionList().getSelectedItem();
                if (o != null && o instanceof String)
                {
                    String s = (String)o;
                    awaitingAction = false;
                    if (host)
                    {
                        serverCommandHandler(cmd, s, username);
                    }
                    else
                    {
                        sendUIActionAsClient(cmd, s);
                    }
                    anyAction = true;
                }
            } // end of Allocate Monster
            else if (e.getActionCommand().equals("actionOk") &&
                    (cmd.equals("!HealTarget") || cmd.equals("!PlayerChoice")))
            {
                // text choices that are handled by sending the chosen string
                Object o = viewManager.getActionList().getSelectedItem();
                if (o != null && o instanceof String)
                {
                    String s = (String)o;
                    awaitingAction = false;
                    if (host)
                    {
                        serverCommandHandler(cmd, s, username);
                    }
                    else
                    {
                        sendUIActionAsClient(cmd, s);
                    }
                    anyAction = true;
                }
            } // end of !AllocateClue
            else if (cmd.equals("!Encounter") && e.getActionCommand().equals("actionOk"))
            {
                String[] tokens = details.split(",");
                int numEncounters = XmlTools.parseInt(tokens[1]);
                String s = "";
                if (numEncounters == 1)
                {
                    s = tokens[2];
                }
                else
                {
                    int index = viewManager.getActionList().getSelectedIndex();
                    if (index >= 0 && 2 + index < tokens.length)
                    {
                        s = tokens[2 + index];
                    }
                }
                if (!s.equals(""))
                {
                    awaitingAction = false;
                    if (host)
                    {
                        serverCommandHandler(cmd, s, username);
                    }
                    else
                    {
                        sendUIActionAsClient(cmd, s);
                    }
                    anyAction = true;
                }
            } // end of !Encounter
            else if (cmd.equals("!OWEncounter") && e.getActionCommand().equals("actionOk"))
            {
                String[] tokens = details.split(",");
                int numEncounters = XmlTools.parseInt(tokens[1]);
                String s = "";
                if (numEncounters == 1)
                {
                    s = tokens[2];
                }
                else
                {
                    Object obj = viewManager.getActionList().getSelectedItem();
                    if (obj instanceof GateCard)
                    {
                        GateCard gc = (GateCard)obj;
                        s = gc.getName();
                    }
                }
                if (!s.equals(""))
                {
                    awaitingAction = false;
                    if (host)
                    {
                        serverCommandHandler(cmd, s, username);
                    }
                    else
                    {
                        sendUIActionAsClient(cmd, s);
                    }
                    anyAction = true;
                }
            } // end of !OWEncounter
            else if (cmd.equals("*Roll"))
            {
                String s = "";
                if (e.getActionCommand().equals("actionUseClue"))
                {
                    s = "Clue";
                }
                else if (e.getActionCommand().equals("actionOk"))
                {
                    s = "Ok";
                }
                else if (e.getActionCommand().equals("actionUseEffect"))
                {
                    Object o = viewManager.getRollPanelList().getSelectedItem();
                    if (o != null && o instanceof ModEffect)
                    {
                        ModEffect mod = (ModEffect)o;
                        Investigator inv = gameData.getInvestigator(actionPanelCommand.player);
                        s = mod.getIdentifier();
                        if (mod.getUsage().equals(EffectUsage.LIMITED_MULTI))
                        {
                            // For limited items, ask user how many times they wish to use it
                            // Although there are none that apply to rolls at this moment.
                            int maxUses = mod.getSource().getLimit() - mod.getSource().getNumCounters();
                            int uses = -1;
                            String valStr = JOptionPane.showInputDialog(this, "How many times " +
                                "would you like to use " + mod.getSource().getName() +
                                "? (Max: " + maxUses + ")");
                            if (valStr == null)
                            {
                                return;
                            }
                            if (!XmlTools.isNumeric(valStr))
                            {
                                JOptionPane.showMessageDialog(this, "Please input a valid number",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            uses = XmlTools.parseInt(valStr);
                            if (uses < 1 || uses > maxUses)
                            {
                                JOptionPane.showMessageDialog(this, "Please input a valid number",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            else
                            {
                                s += "," + uses;
                            }
                        } // end of if 'limited usage'
                        else if (mod.getUsage().equals(EffectUsage.CAST_AND_EXHAUST) ||
                                mod.getUsage().equals(EffectUsage.CAST_AND_DISCARD) &&
                                mod.getSource() instanceof Spell)
                        {
                            // For spells, check if the investigator has enough sanity to cast
                            Spell spell = (Spell)mod.getSource();
                            if (inv.getCurrentSanity() < spell.getSanityCost())
                            {
                                JOptionPane.showMessageDialog(this, "Not enough Sanity to cast " +
                                        spell.getName(), "Error",
                                        JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            else if (inv.getCurrentSanity() == spell.getSanityCost())
                            {
                                int res = JOptionPane.showConfirmDialog(this,
                                        "Casting this spell will cause you to go insane! " +
                                        "Proceed anyway?", "Confirm selection",
                                        JOptionPane.YES_NO_OPTION);
                                if (res != JOptionPane.YES_OPTION)
                                {
                                    return;
                                }
                            }
                        }
                        /* Check selection, if necessary */
                        if (mod.getDetails().equals("Reroll Dice"))
                        {
                            int numDice = mod.getAmountAsInt();
                            int[] indices = viewManager.getDiceList().getSelectedIndices();
                            if (indices.length == 0)
                            {
                                JOptionPane.showMessageDialog(this, "No dice are selected",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            if (indices.length > numDice)
                            {
                                JOptionPane.showMessageDialog(this, "You may reroll at most " +
                                        numDice + " dice.",
                                        "Error", JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            for (int index : indices)
                            {
                                s += "," + index;
                            }
                        }
                        else if (mod.getDetails().equals("Reroll All"))
                        {
                            if (viewManager.getDiceList().getModel().getSize() == 0)
                            {
                                int res = JOptionPane.showConfirmDialog(this,
                                        "You have no dice to reroll! Are you sure you wish" +
                                        " to use this effect?",
                                        "Confirm selection", JOptionPane.YES_NO_OPTION);
                                if (res != JOptionPane.YES_OPTION)
                                {
                                    return;
                                }
                            }
                        }
                    }
                } // end of "actionUseEffect"
                if (!s.equals(""))
                {
                    awaitingAction = false;
                    if (host)
                    {
                        serverCommandHandler("*Roll", s, username);
                    }
                    else
                    {
                        sendUIActionAsClient("*Roll", s);
                    }
                    anyAction = true;
                }
            } // end of *Roll
            else if (cmd.equals("*Combat"))
            {
                String s = "";
                Investigator inv = gameData.getInvestigator(actionPanelCommand.player);
                String[] tokens = details.split(",");
                Monster mon = gameData.getMonsterById(XmlTools.parseInt(tokens[0]));
                int magRes = XmlTools.parseInt(tokens[4]);
                int handsLeft = XmlTools.parseInt(tokens[5]);

                if (e.getActionCommand().equals("actionEvade"))
                {
                    s = "Evade";
                }
                else if (e.getActionCommand().equals("actionOk"))
                {
                    s = "Ok";
                    /* Check that the number of checked items dont exceed the hands left */
                    List<Object> objs = viewManager.getCheckedWeapons();
                    for (Object o : objs)
                    {
                        if (o instanceof EffectSource)
                        {
                            EffectSource effsrc = (EffectSource)o;
                            int hands = GameData.getNumHands(effsrc);
                            handsLeft -= hands;
                            s += "," + effsrc.getIdentifier();
                        }
                        else
                        {
                            AHLogger.error("Invalid item in checked weapons list: " + o.toString());
                        }
                    }
                    if (handsLeft < 0)
                    {
                        JOptionPane.showMessageDialog(this, "You don't have enough hands!",
                                "Not enough hands!", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                }
                else if (e.getActionCommand().equals("actionUseEffect"))
                {
                    Object o = viewManager.getCombatPanelList().getSelectedItem();
                    if (o == null || !(o instanceof ModEffect))
                    {
                        return;
                    }
                    ModEffect mod = (ModEffect)o;
                    s = mod.getIdentifier();
                    /* Check if the investigator has enough hands to use the effect */
                    int hands = GameData.getNumHands(mod.getSource());
                    if (handsLeft < hands)
                    {
                        JOptionPane.showMessageDialog(this, "Not enough hands to use " +
                                mod.getSource().getName() + "!", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (mod.getUsage().equals(EffectUsage.LIMITED_MULTI))
                    {
                        // For limited items, ask user how many times they wish to use it
                        // Although there are none that apply to combat at this moment.
                        int maxUses = mod.getSource().getLimit() - mod.getSource().getNumCounters();
                        int uses = -1;
                        String valStr = JOptionPane.showInputDialog(this, "How many times " +
                            "would you like to use " + mod.getSource().getName() +
                            "? (Max: " + maxUses + ")");
                        if (valStr == null)
                        {
                            return;
                        }
                        if (!XmlTools.isNumeric(valStr))
                        {
                            JOptionPane.showMessageDialog(this, "Please input a valid number",
                                    "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        uses = XmlTools.parseInt(valStr);
                        if (uses < 1 || uses > maxUses)
                        {
                            JOptionPane.showMessageDialog(this, "Please input a valid number",
                                    "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        else
                        {
                            s += "," + uses;
                        }
                    } // end of if 'limited usage'
                    else if (mod.getUsage().equals(EffectUsage.CAST_AND_EXHAUST) ||
                            mod.getUsage().equals(EffectUsage.CAST_AND_DISCARD) &&
                            mod.getSource() instanceof Spell)
                    {
                        // For spells, check if the investigator has enough sanity to cast
                        Spell spell = (Spell)mod.getSource();
                        if (inv.getCurrentSanity() < spell.getSanityCost())
                        {
                            JOptionPane.showMessageDialog(this, "Not enough Sanity to cast " +
                                    spell.getName(), "Error",
                                    JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        else if (inv.getCurrentSanity() == spell.getSanityCost())
                        {
                            int res = JOptionPane.showConfirmDialog(this,
                                    "Casting this spell will cause you to go insane! " +
                                    "Proceed anyway?", "Confirm selection",
                                    JOptionPane.YES_NO_OPTION);
                            if (res != JOptionPane.YES_OPTION)
                            {
                                return;
                            }
                        }
                        // Check that the monster is not immune
                        if (magRes == Globals.IMMUNITY)
                        {
                            int res = JOptionPane.showConfirmDialog(this,
                                    "The monster is immune to magic! " +
                                    "Proceed anyway?", "Confirm selection",
                                    JOptionPane.YES_NO_OPTION);
                            if (res != JOptionPane.YES_OPTION)
                            {
                                return;
                            }
                        }
                    }

                    // Get parameters for Red Sign, if necessary
                    if (mod.getType().equals("Combat Spell") && mod.getDetails().equals("Red Sign"))
                    {
                        List<String> choices = new ArrayList<String>();
                        for (int i = 0; i < mon.getAttributes().size(); i++)
                        {
                            switch (mon.getAttributes().get(i))
                            {
                            case NIGHTMARISH:
                            case NIGHTMARISH2:
                                choices.add("Nightmarish");
                                break;
                            case OVERWHELMING:
                            case OVERWHELMING2:
                                choices.add("Overwhelming");
                                break;
                            case ELUSIVE:
                            case UNDEAD:
                            case MASK:
                                break;
                            default:
                                choices.add(mon.getAttributes().get(i).toString());
                                break;
                            }
                        }
                        if (choices.size() > 1)
                        {
                            Object response = JOptionPane.showInputDialog(this, "Which attribute do " +
                            		"you wish to negate?", "Red Sign of Shudde M'ell",
                            		JOptionPane.QUESTION_MESSAGE, null,
                                    choices.toArray(new String[choices.size()]), null);
                            if (response == null)
                            {
                                return;
                            }
                            else
                            {
                                s += "," + response.toString();
                            }
                        }
                        else if (choices.size() == 1)
                        {
                            s += "," + choices.get(0);
                        }
                        else
                        {
                            s += ",None";
                        }
                    } // end of red sign
                    if (mod.getType().equals("Combat Spell") &&
                            mod.getDetails().equals("Enchant Weapon"))
                    {
                        List<InvestigatorCard> list = inv.getPhysicalWeapons();
                        if (list.isEmpty())
                        {
                            JOptionPane.showMessageDialog(this, "No physical weapons to enchant!",
                                    "Enchant Weapon", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        String[] choices = new String[list.size()];
                        for (int i = 0; i < choices.length; i++)
                        {
                            choices[i] = list.get(i).getName();
                        }
                        Object response = JOptionPane.showInputDialog(this, "Which weapon do " +
                                "you wish to enchant?", "Enchant",
                                JOptionPane.QUESTION_MESSAGE, null, choices, null);
                        if (response == null)
                        {
                            return;
                        }
                        else
                        {
                            s += "," + response.toString();
                        }
                    }
                }

                if (!s.equals(""))
                {
                    awaitingAction = false;
                    if (host)
                    {
                        serverCommandHandler("*Combat", s, username);
                    }
                    else
                    {
                        sendUIActionAsClient("*Combat", s);
                    }
                    anyAction = true;
                }
            }
        } // end of *Combat

        else if (!gameData.isStarted())
        {
            switch (gameData.getGameSetupState())
            {
            case SELECT_INVESTIGATORS:
                if (e.getActionCommand().equals("actionOk"))
                {
                    if (gameData.getCurrentInvestigator() == null)
                    {
                        // check investigator selection
                        Object obj = viewManager.getActionList().getSelectedItem();
                        if (obj != null && obj instanceof Investigator)
                        {
                            Investigator inv = (Investigator)obj;
                            int res = JOptionPane.showConfirmDialog(this,
                                    "You have selected " + inv.getName() + ". Is this correct?",
                                    "Confirm selection", JOptionPane.YES_NO_OPTION);
                            if (res != JOptionPane.YES_OPTION)
                            {
                                return;
                            }
                            if (host)
                            {
                                serverCommandHandler("Select Investigators", inv.getName(),
                                        username);
                            }
                            else
                            {
                                sendUIActionAsClient("Select Investigators", inv.getName());
                            }
                            if (viewManager.getDisplayedInvestigator() == null)
                            {
                                viewManager.updateInvestigatorDisplay(inv);
                            }
                            anyAction = true;
                        }
                    }
                    else
                    {
                        // check skill sliders
                        StringBuffer sb = new StringBuffer();
                        for (int i = 0; i < 3; i++)
                        {
                            if (i != 0) sb.append(",");
                            sb.append(String.valueOf(viewManager.getSkillSliderPosition(i)));
                        }
                        if (host)
                        {
                            serverCommandHandler("Initialize Sliders", sb.toString(), username);
                        }
                        else
                        {
                            sendUIActionAsClient("Initialize Sliders", sb.toString());
                        }
                        anyAction = true;
                    }
                }
                break;
            } // end of switch statement
        }
        else
        {
            switch (gameData.getGamePhase())
            {
            case UPKEEP:
                // check skill sliders
                StringBuffer sb = new StringBuffer();
                for (int i = 0; i < 3; i++)
                {
                    if (i != 0) sb.append(",");
                    sb.append(String.valueOf(viewManager.getSkillSliderPosition(i)));
                }
                awaitingAction = false;
                if (host)
                {
                    serverCommandHandler("Set Sliders", sb.toString(), username);
                }
                else
                {
                    sendUIActionAsClient("Set Sliders", sb.toString());
                }
                anyAction = true;
                break;
            case UPKEEP_EFFECT:
                if (awaitingAction)
                {
                    // send the chosen effect
                    if (e.getActionCommand().equals("actionOk"))
                    {
                        /* Player uses a usable effect */
                        String s = "";
                        Object o = viewManager.getActionList().getSelectedItem();
                        if (o != null && o.equals("None"))
                        {
                            s = "None";
                        }
                        else if (o != null && o instanceof UsableEffect)
                        {
                            UsableEffect ueff = (UsableEffect)o;
                            Investigator inv = gameData.getCurrentInvestigator();
                            s = ueff.getIdentifier();
                            if (ueff.getUsage().equals(EffectUsage.CAST_AND_EXHAUST) ||
                                    ueff.getUsage().equals(EffectUsage.CAST_AND_DISCARD) &&
                                    ueff.getSource() instanceof Spell)
                            {
                                // For spells, check if the investigator has enough sanity to cast
                                Spell spell = (Spell)ueff.getSource();
                                if (inv.getCurrentSanity() < spell.getSanityCost())
                                {
                                    JOptionPane.showMessageDialog(this, "Not enough Sanity to cast " +
                                            spell.getName(), "Error",
                                            JOptionPane.ERROR_MESSAGE);
                                    return;
                                }
                                else if (inv.getCurrentSanity() == spell.getSanityCost())
                                {
                                    int res = JOptionPane.showConfirmDialog(this,
                                            "Casting this spell will cause you to go insane! " +
                                            "Proceed anyway?", "Confirm selection",
                                            JOptionPane.YES_NO_OPTION);
                                    if (res != JOptionPane.YES_OPTION)
                                    {
                                        return;
                                    }
                                }
                            }
                        }
                        awaitingAction = false;
                        if (host)
                        {
                            serverCommandHandler("Use Effects", s, username);
                        }
                        else
                        {
                            sendUIActionAsClient("Use Effects", s);
                        }
                        anyAction = true;
                    }
                }
                break;
            case MOVEMENT:
                if (awaitingAction)
                {
                    Investigator inv = gameData.getCurrentInvestigator();
                    if (e.getActionCommand().equals("actionOk") &&
                            gameData.getMoveState().equals(MoveState.ARKHAM))
                    {
                        /* Ok is pressed in the movement panel. Send the selected
                         * location, if any */
                        String s = selectedLocation != null ? selectedLocation.getName() :
                            inv.getLocationName();
                        awaitingAction = false;
                        if (host)
                        {
                            serverCommandHandler("Movement", s, username);
                        }
                        else
                        {
                            sendUIActionAsClient("Movement", s);
                        }
                        anyAction = true;
                        viewManager.getBoardManager().clearDisplayedPath();
                    }
                    else if (e.getActionCommand().equals("actionOk") &&
                            gameData.getMoveState().equals(MoveState.COMBAT))
                    {
                        String[] options = {"Fight", "Evade", "Cancel"};
                        int val = JOptionPane.showOptionDialog(this, "Would you like to " +
                        		"fight or evade?", "Combat", JOptionPane.YES_NO_CANCEL_OPTION,
                        		JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
                        if (val == JOptionPane.CANCEL_OPTION || val == JOptionPane.CLOSED_OPTION)
                        {
                            return;
                        }
                        Object o = viewManager.getActionList().getSelectedItem();
                        String sendcmd = val == JOptionPane.YES_OPTION ? "Fight Monster" :
                            "Evade Monster";
                        if (o != null && o instanceof Monster)
                        {
                            String s = String.valueOf(((Monster)o).getId());
                            awaitingAction = false;
                            if (host)
                            {
                                serverCommandHandler(sendcmd, s, username);
                            }
                            else
                            {
                                sendUIActionAsClient(sendcmd, s);
                            }
                            anyAction = true;
                        }
                    }
                    else if (gameData.getMoveState().equals(MoveState.TRADE) &&
                            (e.getActionCommand().equals("actionOk") ||
                            e.getActionCommand().equals("actionCancel")))
                    {
                        /* Ok or cancel is pressed in the trade menu */
                        List<Object> objlist1 = viewManager.getCheckedItems1();
                        List<Object> objlist2 = viewManager.getCheckedItems2();
                        String sendcmd = "Trade";
                        sb = new StringBuffer();
                        if ((objlist1.isEmpty() && objlist2.isEmpty())
                                || e.getActionCommand().equals("actionCancel"))
                        {
                            /* Trade is cancelled */
                            chatManager.displayMessage(">> Trade cancelled");
                            sendcmd = "Cancel Trade";
                            sb.append("None");
                        }
                        else
                        {
                            for (int i = 0; i < objlist1.size(); i++)
                            {
                                InvestigatorCard card = (InvestigatorCard)objlist1.get(i);
                                if (i != 0)
                                {
                                    sb.append(",");
                                }
                                sb.append(card.getId());
                            }
                            sb.append(";");
                            for (int i = 0; i < objlist2.size(); i++)
                            {
                                InvestigatorCard card = (InvestigatorCard)objlist2.get(i);
                                if (i != 0)
                                {
                                    sb.append(",");
                                }
                                sb.append(card.getId());
                            }
                        }
                        awaitingAction = false;
                        if (host)
                        {
                            serverCommandHandler(sendcmd, sb.toString(), username);
                        }
                        else
                        {
                            sendUIActionAsClient(sendcmd, sb.toString());
                        }
                        viewManager.getBoardManager().clearDisplayedPath();
                        anyAction = true;
                    }
                    else if (gameData.getMoveState().equals(MoveState.ACCEPTING_TRADE))
                    {
                        /* Player presses OK or Cancel */
                        String s = "";
                        if (e.getActionCommand().equals("actionOk"))
                        {
                            s = "Yes";
                        }
                        else if (e.getActionCommand().equals("actionCancel"))
                        {
                            s = "No";
                        }
                        if (s.equals(""))
                        {
                            AHLogger.error("actionPerformed", "Unknown action performed in " +
                            		"accepting trade: " + e.getActionCommand());
                            return;
                        }
                        awaitingAction = false;
                        if (host)
                        {
                            serverCommandHandler("Accept Trade", s, username);
                        }
                        else
                        {
                            sendUIActionAsClient("Accept Trade", s);
                        }
                        anyAction = true;
                    }
                    else if (e.getActionCommand().equals("actionUseEffect"))
                    {
                        String s = "";
                        Object o = viewManager.getMovePanelList().getSelectedItem();
                        if (o != null && o instanceof UsableEffect)
                        {
                            UsableEffect ueff = (UsableEffect)o;
                            s = ueff.getIdentifier();
                            if (ueff.getUsage().equals(EffectUsage.CAST_AND_EXHAUST) ||
                                    ueff.getUsage().equals(EffectUsage.CAST_AND_DISCARD) &&
                                    ueff.getSource() instanceof Spell)
                            {
                                // For spells, check if the investigator has enough sanity to cast
                                Spell spell = (Spell)ueff.getSource();
                                if (inv.getCurrentSanity() < spell.getSanityCost())
                                {
                                    JOptionPane.showMessageDialog(this, "Not enough Sanity to cast " +
                                            spell.getName(), "Error",
                                            JOptionPane.ERROR_MESSAGE);
                                    return;
                                }
                                else if (inv.getCurrentSanity() == spell.getSanityCost())
                                {
                                    int res = JOptionPane.showConfirmDialog(this,
                                            "Casting this spell will cause you to go insane! " +
                                            "Proceed anyway?", "Confirm selection",
                                            JOptionPane.YES_NO_OPTION);
                                    if (res != JOptionPane.YES_OPTION)
                                    {
                                        return;
                                    }
                                }
                            }
                        }
                        awaitingAction = false;
                        if (host)
                        {
                            serverCommandHandler("Use Effects", s, username);
                        }
                        else
                        {
                            sendUIActionAsClient("Use Effects", s);
                        }
                        viewManager.getBoardManager().clearDisplayedPath();
                        anyAction = true;
                    }
                    else if (e.getActionCommand().equals("actionTrade"))
                    {
                        Object o = viewManager.getActionList().getSelectedItem();
                        if (o != null && o instanceof String)
                        {
                            String s = (String)o;
                            awaitingAction = false;
                            if (host)
                            {
                                serverCommandHandler("Initiate Trade", s, username);
                            }
                            else
                            {
                                sendUIActionAsClient("Initiate Trade", s);
                            }
                            anyAction = true;
                            viewManager.getBoardManager().clearDisplayedPath();
                        }
                    }
                }
                break;
            } // end of switch

        } // end of gameStarted

        if (host && anyAction)
        {
            /* After the host performs any action, advance to next active
             * player if any, then advance game flow.
             */
            advanceGameFlow();
            nextActivePlayer();
            viewManager.updateDisplay();
            viewManager.updateTurnOrder(gameData.getCurrentPlayer(), activePlayer);
        }
    }

    /* ------------------ Game logic code ---------------------- */
    /* The game logic code is handled in sections. For each section,
     * the server and the client code will be listed for convenience
     * and easy reference.
     *
     * Architecture of messages is such that the server will send
     * out commands to update the client's game state, and utilize
     * the command message to indicate which player should respond.
     *
     * Clients will wait for the server's command before advancing
     * the game state.
     */

    /**
     * Advances the game forward if the current step is completed. This
     * method is used by the server only to keep track of game flow.
     */
    private void advanceGameFlow()
    {
        if (!gameData.isStarted())
        {
            /* Pre-game events */
            switch (gameData.getGameSetupState())
            {
            case PRE_SETUP:
                // wait for "ready (gameLoaded)" from clients
                if (readyChecker.isReady("gameLoaded"))
                {
                    readyChecker.unregister("gameLoaded");
                    gameData.setGameSetupState(GameSetupState.SELECT_INVESTIGATORS);
                    startInvestigatorSelection();
                }
                break;
            case SELECT_INVESTIGATORS:
                if (gameData.getCurrentInvestigator() != null)
                {
                    Investigator inv = gameData.getCurrentInvestigator();
                    if (inv.areSkillSlidersSet())
                    {
                        StringBuffer sb = new StringBuffer();
                        for (int i = 0; i < inv.getSkillSliders().length; i++)
                        {
                            if (i != 0) sb.append(",");
                            sb.append(String.valueOf(inv.getSkillSlider(i)));
                        }
                        boolean res = gameData.advanceTurnOrder();
                        CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        cmsg.addCommand("Initialize Sliders", sb.toString());
                        server.serverCommandMessage(cmsg);
                        if (!res)
                        {
                            updateActionForInvSelection();
                        }
                        else
                        {
                            gameData.resetTurnOrder(gameData.getStartingPlayer());
                            gameData.setGameSetupState(GameSetupState.SELECT_ANCIENT_ONE);
                            viewManager.getSlidingPanel().setExtended(false);
                            advanceGameFlow();
                        }
                    }
                    else
                    {
                        CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        String s = inv.getName();
                        for (InvestigatorCard card : inv.getInvcards())
                        {
                            s += "," + card.getId();
                        }
                        cmsg.addCommand("Select Investigators", s);
                        server.serverCommandMessage(cmsg);
                        if (amOwner[gameData.getCurrentPlayer()])
                        {
                            viewManager.updateForSliderAdjustment(inv, true);
                        }
                    }
                }
                break;
            case SELECT_ANCIENT_ONE:
                if (gameData.getAncientOne() == null)
                {
                    CommandMessage cmsg = new CommandMessage(-1);
                    AncientOne ao = gameData.randomizeAncientOne();
                    cmsg.addCommand("Select Ancient One", ao.getName());
                    chatManager.displayMessage("# " + ao.getName() +
                            " is selected as the Ancient One.");
                    viewManager.updateForNoAction("Select Ancient One",
                            "Please wait while the Ancient One is selected.");
                    readyChecker.register("aoSelect", username);
                    server.serverCommandMessage(cmsg);
                    advanceGameFlow();
                }
                else if (readyChecker.isReady("aoSelect"))
                {
                    readyChecker.unregister("aoSelect");
                    gameData.setGameSetupState(GameSetupState.RECEIVE_STARTING_ITEMS);
                    CommandMessage cmsg = new CommandMessage(0);
                    cmsg.addCommand("Receive Starting Items", "begin");
                    distributeRandomItems(cmsg, 0);
                    resolveEvent(cmsg, null);
                    checkForUnresolvedEvents(cmsg);
                    server.serverCommandMessage(cmsg);
                    if (!awaitingAction)
                    {
                        viewManager.updateForNoAction("Starting Items", "Please wait while the other" +
                            " investigators receive their starting items.");
                    }
                    advanceGameFlow();
                }
                break;
            case RECEIVE_STARTING_ITEMS:
                if (eventStack.isEmpty())
                {
                    /* No outstanding items on stack - item distribution complete */
                    boolean res = gameData.advanceTurnOrder();
                    if (!res)
                    {
                        CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        cmsg.addCommand("Receive Starting Items", "begin");
                        distributeRandomItems(cmsg, gameData.getCurrentPlayer());
                        resolveEvent(cmsg, null);
                        checkForUnresolvedEvents(cmsg);
                        server.serverCommandMessage(cmsg);
                        if (!awaitingAction)
                        {
                            viewManager.updateForNoAction("Starting Items", "Please wait while the other" +
                                " investigators receive their starting items.");
                        }
                    }
                    else
                    {
                        gameData.setGameSetupState(GameSetupState.FIRST_MYTHOS);
                        gameData.resetTurnOrder(gameData.getStartingPlayer());
                        CommandMessage cmsg = new CommandMessage(0);
                        cmsg.addCommand("ready", "firstMythos");
                        readyChecker.register("firstMythos", username);
                        server.serverCommandMessage(cmsg);
                        viewManager.updateTurnOrder(gameData.getCurrentPlayer(), activePlayer);
                        viewManager.updateForNoAction("Mythos", "Please wait while a mythos card " +
                                "is drawn.");
                    }
                    advanceGameFlow();
                }
                break;
            case FIRST_MYTHOS:
                if (readyChecker.isReady("firstMythos"))
                {
                    // draw first mythos card
                    gameData.startGame();
                    gameData.setGamePhase(GamePhase.MYTHOS_GATE);
                    readyChecker.unregister("firstMythos");
                    CommandMessage cmsg = new CommandMessage(gameData.getStartingPlayer());
                    drawMythosGate(cmsg);
                    resolveEvent(cmsg, null);
                    checkForUnresolvedEvents(cmsg);
                    server.serverCommandMessage(cmsg);
                    viewManager.getInfoDisplay().displayMythosCard(gameData.getLastDrawnMythos());
                    advanceGameFlow();
                }
                break;
            }
        }
        else
        { // gamedata is started
            switch (gameData.getGamePhase())
            {
            case PRE_UPKEEP:
                if (eventStack.isEmpty())
                {
                    CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                    cmsg.addCommand("Pre-Upkeep", "done");
                    gameData.setGamePhase(GamePhase.UPKEEP);
                    for (Investigator inv : gameData.getInvestigators())
                    {
                        inv.setSkillSliderFlag(false);
                    }
                    resetActionCompleted();
                    cmsg.addCommand("Set Sliders", "Upkeep");
                    server.serverCommandMessage(cmsg);
                    updateActionForUpkeep(amOwner[gameData.getCurrentPlayer()]);
                }
                break;
            case UPKEEP:
                if (gameData.getCurrentInvestigator().areSkillSlidersSet())
                {
                    Investigator inv = gameData.getCurrentInvestigator();
                    StringBuffer sb = new StringBuffer();
                    for (int i = 0; i < inv.getSkillSliders().length; i++)
                    {
                        if (i != 0) sb.append(",");
                        sb.append(String.valueOf(inv.getSkillSlider(i)));
                    }
                    CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                    cmsg.addCommand("Slider Positions", sb.toString());
                    List<UsableEffect> ueff = inv.searchUsableEffects(UsablePhase.UPKEEP);
                    GameData.removeUnavailableEffects(inv, ueff);
                    if (ueff.isEmpty())
                    {
                        actionCompleted[gameData.getCurrentPlayer()] = true;
                        boolean res = gameData.advanceTurnOrder();
                        if (!res)
                        {
                            cmsg.addCommand("Set Sliders", "Upkeep", gameData.getCurrentPlayer());
                            updateActionForUpkeep(amOwner[gameData.getCurrentPlayer()]);
                        }
                        else
                        {
                            cmsg.addCommand("Upkeep", "done", gameData.getCurrentPlayer());
                            resetActionCompleted();
                            gameData.setGamePhase(GamePhase.MOVEMENT);
                            for (Investigator inv2 : gameData.getInvestigators())
                            {
                                gameData.prepareForMovement(inv2);
                            }
                            gameData.setMoveState(MoveState.ARKHAM);
                            server.serverCommandMessage(cmsg);
                            advanceGameFlow();
                            break;
                        }
                    }
                    else
                    {
                        cmsg.addCommand("Use Effects", "Upkeep");
                        gameData.setGamePhase(GamePhase.UPKEEP_EFFECT);
                        updateActionForUpkeepEffects(amOwner[gameData.getCurrentPlayer()]);
                    }
                    server.serverCommandMessage(cmsg);
                }
                break;
            case UPKEEP_EFFECT:
                if (eventStack.isEmpty())
                {
                    Investigator inv = gameData.getCurrentInvestigator();
                    List<UsableEffect> ueff = inv.searchUsableEffects(UsablePhase.UPKEEP);
                    GameData.removeUnavailableEffects(inv, ueff);
                    if (actionCompleted[gameData.getCurrentPlayer()] || ueff.isEmpty())
                    {
                        // If there are no usable effects or the player chooses not to use
                        // any, move on to the next player
                        boolean res = gameData.advanceTurnOrder();
                        CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        if (!res)
                        {
                            cmsg.addCommand("Set Sliders", "Upkeep");
                            gameData.setGamePhase(GamePhase.UPKEEP);
                            updateActionForUpkeep(amOwner[gameData.getCurrentPlayer()]);
                            server.serverCommandMessage(cmsg);
                        }
                        else
                        {
                            cmsg.addCommand("Upkeep", "done", gameData.getCurrentPlayer());
                            resetActionCompleted();
                            gameData.setGamePhase(GamePhase.MOVEMENT);
                            for (Investigator inv2 : gameData.getInvestigators())
                            {
                                gameData.prepareForMovement(inv2);
                            }
                            gameData.setMoveState(MoveState.ARKHAM);
                            server.serverCommandMessage(cmsg);
                            advanceGameFlow();
                        }
                    }
                    else
                    {
                        CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        cmsg.addCommand("Use Effects", "Upkeep");
                        server.serverCommandMessage(cmsg);
                        updateActionForUpkeepEffects(amOwner[gameData.getCurrentPlayer()]);
                    }
                }
                break;
            case MOVEMENT:
                if (eventStack.isEmpty())
                {
                    Investigator inv = gameData.getCurrentInvestigator();
                    /*
                     * Check if this investigator is dead
                     */
                    if (inv.isDead())
                    {
                        gameData.setMoveState(MoveState.NONE);
                        AHLogger.info(inv.getName() + "'s movement is over as he/she is dead.");
                    }
                    switch (gameData.getMoveState())
                    {
                    case NONE:
                        // Advance to next player
                        boolean res = gameData.advanceTurnOrder();
                        CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        if (!res)
                        {
                            cmsg.addCommand("Do Movement", "Movement", gameData.getCurrentPlayer());
                            gameData.prepareForMovement(gameData.getCurrentInvestigator());
                            gameData.setMoveState(MoveState.ARKHAM);
                            updateActionForMovement(amOwner[gameData.getCurrentPlayer()]);
                            server.serverCommandMessage(cmsg);
                        }
                        else
                        {
                            /* Done with movement phase, move to Arkham Encounter phase*/
                            resetActionCompleted();
                            gameData.setGamePhase(GamePhase.ARKHAM_ENCOUNTER);
                            cmsg.addCommand("Movement", "done", gameData.getCurrentPlayer());
                            server.serverCommandMessage(cmsg);
                            gameData.clearCurrentEvent();
                            advanceGameFlow();
                        }
                        break;
                    case ARKHAM:
                        cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        cmsg.addCommand("Do Movement", "Movement", gameData.getCurrentPlayer());
                        updateActionForMovement(amOwner[gameData.getCurrentPlayer()]);
                        server.serverCommandMessage(cmsg);
                        break;
                    case COMBAT:
                        if (gameData.getCombatFrame().isEmpty())
                        {
                            /* All monsters are dealt with */
                            inv.setAvoidedMonsters(true);
                            gameData.setMoveState(MoveState.ARKHAM);
                            cmsg = new CommandMessage(gameData.getCurrentPlayer());
                            cmsg.addCommand("End Combat", "None");
                            server.serverCommandMessage(cmsg);
                            advanceGameFlow();
                        }
                        else
                        {
                            /* As long as there are still monsters to deal with */
                            StringBuffer sb = new StringBuffer();
                            for (Monster m : gameData.getCombatFrame())
                            {
                                if (sb.length() > 0)
                                {
                                    sb.append(",");
                                }
                                sb.append(m.getId());
                            }
                            cmsg = new CommandMessage(gameData.getCurrentPlayer());
                            cmsg.addCommand("Start Combat", sb.toString());
                            updateActionForStartCombat(amOwner[gameData.getCurrentPlayer()]);
                            server.serverCommandMessage(cmsg);
                        }
                        break;
                    case JUST_RETURNED:
                        break;
                    case CHOOSE_RETURN_GATE:
                        break;
                    case TRADE:
                        cmsg = new CommandMessage(gameData.getCurrentPlayer());
                        cmsg.addCommand("Trade Items", gameData.getTradeRecipient().getName());
                        updateActionForTrade(amOwner[gameData.getCurrentPlayer()]);
                        server.serverCommandMessage(cmsg);
                        break;
                    case ACCEPTING_TRADE:
                        cmsg = new CommandMessage(gameData.getTradeRecipient().getIndex());
                        cmsg.addCommand("Accept Trade", gameData.getTradeDetails());
                        updateActionForTradeAccept(amOwner[gameData.getTradeRecipient().getIndex()]);
                        server.serverCommandMessage(cmsg);
                        break;
                    }
                }
                break;
            case ARKHAM_ENCOUNTER:
                if (eventStack.isEmpty())
                {
                    int index = gameData.getCurrentPlayer();
                    Investigator inv = gameData.getCurrentInvestigator();
                    if (!inv.getLocation().isArkhamLocation())
                    {
                        /* No Arkham encounter phase if investigator is not in Arkham */
                        actionCompleted[index] = true;
                    }
                    if (actionCompleted[index])
                    {
                        boolean res = gameData.advanceTurnOrder();
                        gameData.clearCurrentEvent();
                        if (res)
                        {
                            resetActionCompleted();
                            gameData.setGamePhase(GamePhase.OTHER_WORLD_ENCOUNTER);
                            CommandMessage cmsg = new CommandMessage(index);
                            cmsg.addCommand("Arkham Encounter", "done", index);
                            server.serverCommandMessage(cmsg);
                            advanceGameFlow();
                        }
                        else
                        {
                            CommandMessage cmsg = new CommandMessage(index);
                            cmsg.addCommand("Arkham Encounter", "done", index);
                            server.serverCommandMessage(cmsg);
                            advanceGameFlow();
                        }
                    }
                    else
                    {
                        EventNode en = EventUtils.createEffectNode("Arkham Encounter",
                                inv.getLocationName(), null);
                        EventState es = new EventState(en, EventStateType.NEW_EVENT, index);
                        eventStack.push(es);
                        CommandMessage cmsg = new CommandMessage(index);
                        resolveEvent(cmsg, null);
                        checkForUnresolvedEvents(cmsg);
                        server.serverCommandMessage(cmsg);
                        actionCompleted[index] = true;
                        advanceGameFlow();
                    }
                }
                break;
            case OTHER_WORLD_ENCOUNTER:
                if (eventStack.isEmpty())
                {
                    int index = gameData.getCurrentPlayer();
                    Investigator inv = gameData.getCurrentInvestigator();
                    Pair<OtherWorld, Integer> loc = gameData.parseOWLocation(inv.getLocationName());
                    if (loc == null)
                    {
                        /* No OW encounter phase if investigator is not in an other world */
                        actionCompleted[index] = true;
                    }
                    if (actionCompleted[index])
                    {
                        boolean res = gameData.advanceTurnOrder();
                        gameData.clearCurrentEvent();
                        if (res)
                        {
                            // end of other world encounter phase
                        }
                        else
                        {
                            CommandMessage cmsg = new CommandMessage(index);
                            cmsg.addCommand("Other World Encounter", "done", index);
                            server.serverCommandMessage(cmsg);
                            advanceGameFlow();
                        }
                    }
                    else
                    {
                        EventNode en = EventUtils.createEffectNode("Other World Encounter",
                                loc.getA().getName(), null);
                        EventState es = new EventState(en, EventStateType.NEW_EVENT, index);
                        eventStack.push(es);
                        CommandMessage cmsg = new CommandMessage(index);
                        resolveEvent(cmsg, null);
                        checkForUnresolvedEvents(cmsg);
                        server.serverCommandMessage(cmsg);
                        actionCompleted[index] = true;
                        advanceGameFlow();
                    }
                }
                break;
            case MYTHOS_GATE:
                if (eventStack.isEmpty())
                {
                    /* Gate and monsters are settled, move to next step */
                    gameData.setGamePhase(GamePhase.MYTHOS_CLUE);
                    CommandMessage cmsg = new CommandMessage(gameData.getStartingPlayer());
                    doMythosClue(cmsg);
                    resolveEvent(cmsg, null);
                    checkForUnresolvedEvents(cmsg);
                    server.serverCommandMessage(cmsg);
                    advanceGameFlow();
                }
                break;
            case MYTHOS_CLUE:
                if (eventStack.isEmpty())
                {
                    /* Clue has been given out, do monster movement */
                    CommandMessage cmsg = new CommandMessage(gameData.getStartingPlayer());
                    cmsg.addCommand("Mythos Clue", "done");
                    server.serverCommandMessage(cmsg);
                    gameData.setGamePhase(GamePhase.MYTHOS_MOVEMENT);
                    gameData.prepareMonsterMovement();
                    advanceGameFlow();
                }
                break;
            case MYTHOS_MOVEMENT:
                if (gameData.getUnassignedMonsters().size() > 0)
                {
                    /* Check for unassigned monsters and request from starting player */
                    Pair<Monster, List<Investigator>> pair = gameData.getUnassignedMonsters().get(0);
                    StringBuffer sb = new StringBuffer(pair.getA().getId());
                    for (Investigator inv : pair.getB())
                    {
                        sb.append("," + inv.getName());
                    }
                    int splayer = gameData.getStartingPlayer();
                    if (amOwner[splayer])
                    {
                        setupActionPanel("Allocate Monster", sb.toString(), splayer);
                    }
                    else
                    {
                        CommandMessage cmsg = new CommandMessage(splayer);
                        cmsg.addCommand("Allocate Monster", sb.toString());
                        server.serverCommandMessage(cmsg, gameData.getPlayerName(splayer));
                    }
                }
                else
                {
                    StringBuffer sb = new StringBuffer();
                    for (Map.Entry<Monster, LocationContainer> entry :
                        gameData.getMonsterDestinations().entrySet())
                    {
                        gameData.moveMonsterTo(entry.getKey(), entry.getValue());
                        if (sb.length() > 0) sb.append(",");
                        sb.append(entry.getKey().getId() + "," + entry.getValue().getName());
                        //TODO: account for monsters moving into vortices
                    }
                    CommandMessage cmsg = new CommandMessage(gameData.getStartingPlayer());
                    cmsg.addCommand("Mythos Movement", sb.toString());
                    gameData.setGamePhase(GamePhase.MYTHOS_SPECIAL);
                    doSpecialMovement(cmsg);
                    resolveEvent(cmsg, null);
                    checkForUnresolvedEvents(cmsg);
                    server.serverCommandMessage(cmsg);
                    advanceGameFlow();
                }
                break;
            case MYTHOS_SPECIAL:
                if (eventStack.isEmpty())
                {
                    // All mythos special movement done, start doing mythos effect
                    CommandMessage cmsg = new CommandMessage(gameData.getStartingPlayer());
                    cmsg.addCommand("Mythos Special", "done");
                    gameData.setGamePhase(GamePhase.MYTHOS_EFFECT);
                    doMythosEffect(cmsg);
                    resolveEvent(cmsg, null);
                    checkForUnresolvedEvents(cmsg);
                    server.serverCommandMessage(cmsg);
                    advanceGameFlow();
                }
                break;
            case MYTHOS_EFFECT:
                if (eventStack.isEmpty())
                {
                    /* Turn is over, advance the turn and the starting player,
                     * unless it is turn 1.
                     */
                    gameData.setGamePhase(GamePhase.PRE_UPKEEP);
                    gameData.advanceGameTurn();
                    if (gameData.getGameTurn() > 1)
                    {
                        gameData.advanceStartingPlayer();
                    }
                    gameData.resetTurnOrder(gameData.getStartingPlayer());
                    CommandMessage cmsg = new CommandMessage(gameData.getStartingPlayer());
                    cmsg.addCommand("Mythos Effect", "done");
                    gameData.refreshInvestigators();
                    doPreUpkeep(cmsg);
                    resolveEvent(cmsg, null);
                    checkForUnresolvedEvents(cmsg);
                    server.serverCommandMessage(cmsg);
                    advanceGameFlow();
                }
                break;
            }
        }
    }

    /**
     * The handler on the server side for game commands
     * @param command the command
     * @param details the details
     * @param sender the sender
     */
    private void serverCommandHandler(String cmd, String details, String sender)
    {
        AHLogger.info("Processing command from " + sender + ": [" + cmd + "," + details + "]");
        if (!gameData.isStarted())
        {
            switch (gameData.getGameSetupState())
            {
            case PRE_SETUP:
                // wait for "ready (gameLoaded)" from clients
                if (cmd.equals("ready"))
                {
                    readyChecker.readyReceived(details, sender);
                }
                break;
            case SELECT_INVESTIGATORS:
                // validate name
                String name = gameData.getCurrentPlayerName();
                if (!name.equals(sender))
                {
                    return;
                }
                if (cmd.equals("Select Investigators"))
                {
                    String[] tokens = details.split(",");
                    Investigator inv = GameData.retrieveByName(gameData.getInvestigatorPool(),
                            tokens[0]);
                    gameData.initializeInvestigator(inv);
                    gameData.assignInvestigator(gameData.getCurrentPlayer(), inv);
                    displayMetaInfo(gameData.getCurrentPlayerName(), gameData.getCurrentPlayerName() +
                            " has selected " + inv.getName());
                }
                else if (cmd.equals("Initialize Sliders"))
                {
                    Investigator inv = gameData.getCurrentInvestigator();
                    String[] tokens = details.split(",");
                    inv.setSkillSliders(tokens);
                }
                break;
            case SELECT_ANCIENT_ONE:
                // wait for everyone to "ready (aoSelect)"
                if (cmd.equals("ready"))
                {
                    readyChecker.readyReceived(details, sender);
                }
                break;
            case RECEIVE_STARTING_ITEMS:
                if (!eventStack.isEmpty() && cmd.startsWith("!"))
                {
                    CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                    resolveEvent(cmsg, new Pair<String, String>(cmd, details));
                    checkForUnresolvedEvents(cmsg);
                    server.serverCommandMessage(cmsg);
                }
                break;
            case FIRST_MYTHOS:
                if (cmd.equals("ready"))
                {
                    readyChecker.readyReceived(details, sender);
                }
                break;
            }
        }
        else
        {
            if (!eventStack.isEmpty() && (cmd.startsWith("!") || cmd.startsWith("*")))
            {
                CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                if (sender.equals(gameData.getPlayerName(eventStack.peek().getPlayer())))
                {
                    resolveEvent(cmsg, new Pair<String, String>(cmd, details));
                    checkForUnresolvedEvents(cmsg);
                    if (cmsg.getCommands().size() > 0)
                    {
                        server.serverCommandMessage(cmsg);
                    }
                }
                else
                {
                    //TODO: deal with out of turn events
                }
            }
            else
            {
                /* game has started */
                switch (gameData.getGamePhase())
                {
                case UPKEEP:
                    if (cmd.equals("Set Sliders"))
                    {
                        Investigator inv = gameData.getCurrentInvestigator();
                        String[] tokens = details.split(",");
                        inv.setSkillSliders(tokens);
                    }
                    break;
                case UPKEEP_EFFECT:
                    if (cmd.equals("Use Effects") && sender.equals(gameData.getCurrentPlayerName()))
                    {
                        Investigator inv = gameData.getCurrentInvestigator();
                        if (details.equals("None"))
                        {
                            actionCompleted[gameData.getCurrentPlayer()] = true;
                        }
                        else
                        {
                            String[] tokens = details.split(",");
                            int uid = XmlTools.parseInt(tokens[1]);
                            UsableEffect ueff = inv.searchUsableEffect(tokens[0], uid);
                            if (ueff == null)
                            {
                                return;
                            }
                            CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                            inv.setCurrentUsedEffect(ueff);
                            gameData.consumeUsableEffect(inv, ueff);
                            cmsg.addCommand("#UConsume", ueff.getIdentifier());
                            if (ueff.requiresCast())
                            {
                                if (!(ueff.getSource() instanceof Spell))
                                {
                                    AHLogger.error("resolveModUsage", "Cast type specified " +
                                            " on a non-spell mod-effect");
                                    return;
                                }
                                Spell spell = (Spell)ueff.getSource();
                                EventState es = new EventState(ueff,
                                        EventStateType.USABLE_SPELL, gameData.getCurrentPlayer());
                                EventState evstate = new EventState(spell,
                                        EventStateType.SPELL_ACTIVATION, gameData.getCurrentPlayer());
                                evstate.setPrevState(es);
                                eventStack.push(evstate);
                            }
                            else
                            {
                                /* Execute the chosen usable effect */
                                EventState es = new EventState(ueff.getEvent(),
                                        EventStateType.NEW_EVENT, gameData.getCurrentPlayer());
                                eventStack.push(es);
                            }
                            resolveEvent(cmsg, null);
                            checkForUnresolvedEvents(cmsg);
                            if (cmsg.getCommands().size() > 0)
                            {
                                server.serverCommandMessage(cmsg);
                            }
                        }

                    }
                    break;
                case MOVEMENT:
                    if (sender.equals(gameData.getCurrentPlayerName()) &&
                            gameData.getMoveState().equals(MoveState.ARKHAM))
                    {
                        if (cmd.equals("Movement"))
                        {
                            Investigator inv = gameData.getCurrentInvestigator();
                            CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                            if (inv.getLocation().getNumMonsters() > 0 && !inv.hasAvoidedMonsters())
                            {
                                /* Enter combat */
                                gameData.setMoveState(MoveState.COMBAT);
                                gameData.prepareCombatFrame(inv.getLocation().getMonsterList());
                            }
                            else if (details.equals(inv.getLocationName()))
                            {
                                /* Investigator stays in current spot, end movement, and
                                 * set the movestate */
                                MoveState ms = gameData.endMovement(inv);
                                gameData.setMoveState(ms);
                                switch (ms)
                                {
                                case NONE:
                                    cmsg.addCommand("End Movement", inv.getLocationName() +
                                            "," + inv.isDelayed());
                                    if (inv.getLocationName().equals(Board.LITAS_NAME))
                                    {
                                        // check for LITAS
                                        // TODO: Yog-sothoth's devouring effect
                                        addMessageCommand(cmsg, inv.getName() + " is lost in time " +
                                                "and space as there is no gate to return to!",
                                                inv.getIndex());
                                        // NOTE: Mark Harrigan never gets delayed
                                        inv.setDelayed(true);
                                    }
                                    else if (inv.getLocation().isArkhamLocation())
                                    {
                                        Location loc = (Location)inv.getLocation();
                                        if (loc.getNumClues() > 0)
                                        {
                                            cmsg.addCommand("#AddClue", loc.getNumClues() + "",
                                                    inv.getIndex());
                                            cmsg.addCommand("#LocationClueless", inv.getLocationName(), -1);
                                            inv.modifyClues(loc.getNumClues());
                                            loc.setClues(0);
                                        }
                                    }
                                    break;
                                case JUST_RETURNED:

                                    break;
                                case CHOOSE_RETURN_GATE:

                                    break;
                                }
                            }
                            else if (inv.getLocation() instanceof Location)
                            {
                                /* Investigator chooses to move */
                                Location loc = gameData.getLocation(details);
                                if (loc == null)
                                {
                                    return;
                                }
                                Path path = gameData.computePath((Location)inv.getLocation(), loc,
                                        inv.getMovementPoints(), inv.hasAvoidedMonsters());
                                if (path == null)
                                {
                                    return;
                                }
                                /* Find actual destination */
                                if (path.getBlockedNodes() > 0)
                                {
                                    loc = path.lastUnblockedLocation();
                                }
                                gameData.moveInvestigatorTo(inv, loc);
                                inv.setAvoidedMonsters(false);
                                inv.useMovementPoints(path.getUnblockedLength());
                                cmsg.addCommand("Move To", loc.getName() + "," +
                                        path.getUnblockedLength());
                            }
                            if (cmsg.getCommands().size() > 0)
                            {
                                server.serverCommandMessage(cmsg);
                            }
                        } // end of "Movement" command
                        else if (cmd.equals("Initiate Trade"))
                        {
                            Investigator inv = gameData.getCurrentInvestigator();
                            Investigator tinv = gameData.getInvestigatorByName(details);
                            if (tinv != null && tinv.getLocationName().equals(inv.getLocationName()))
                            {
                                gameData.setTradeRecipient(tinv);
                                gameData.setMoveState(MoveState.TRADE);
                            }
                        } // end of "Initiate Trade" command
                        else if (cmd.equals("Use Effects"))
                        {
                            Investigator inv = gameData.getCurrentInvestigator();
                            String[] tokens = details.split(",");
                            int uid = XmlTools.parseInt(tokens[1]);
                            UsableEffect ueff = inv.searchUsableEffect(tokens[0], uid);
                            if (ueff == null)
                            {
                                return;
                            }
                            CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                            inv.setCurrentUsedEffect(ueff);
                            gameData.consumeUsableEffect(inv, ueff);
                            cmsg.addCommand("#UConsume", ueff.getIdentifier());
                            //TODO: check for insanity/unconsciousness
                            // check for insanity/unconsciousness
                            if (checkForDeath(gameData.getCurrentPlayer()))
                            {
                                // do nothing, event has already been pushed
                            }
                            else if (ueff.requiresCast())
                            {
                                if (!(ueff.getSource() instanceof Spell))
                                {
                                    AHLogger.error("resolveModUsage", "Cast type specified " +
                                            " on a non-spell mod-effect");
                                    return;
                                }
                                // TODO: deal with spells
                                Spell spell = (Spell)ueff.getSource();
                                EventState es = new EventState(ueff,
                                        EventStateType.USABLE_SPELL, gameData.getCurrentPlayer());
                                EventState evstate = new EventState(spell,
                                        EventStateType.SPELL_ACTIVATION, gameData.getCurrentPlayer());
                                evstate.setPrevState(es);
                                eventStack.push(evstate);
                            }
                            else
                            {
                                /* Execute the chosen usable effect */
                                EventState es = new EventState(ueff.getEvent(),
                                        EventStateType.NEW_EVENT, gameData.getCurrentPlayer());
                                eventStack.push(es);
                            }
                            resolveEvent(cmsg, null);
                            checkForUnresolvedEvents(cmsg);
                            if (cmsg.getCommands().size() > 0)
                            {
                                server.serverCommandMessage(cmsg);
                            }
                        } // end of Use Effects
                    } // end of movestate == ARKHAM
                    else if (sender.equals(gameData.getCurrentPlayerName()) &&
                        gameData.getMoveState().equals(MoveState.TRADE))
                    {
                        if (cmd.equals("Cancel Trade"))
                        {
                            gameData.setMoveState(MoveState.ARKHAM);
                        }
                        else if (cmd.equals("Trade"))
                        {
                            Investigator inv = gameData.getCurrentInvestigator();
                            Investigator tinv = gameData.getTradeRecipient();
                            boolean valid = gameData.validateTrade(details, inv, tinv, null, null);
                            if (valid)
                            {
                                gameData.setTradeDetails(details);
                                gameData.setMoveState(MoveState.ACCEPTING_TRADE);
                            }
                            else
                            {
                                gameData.setMoveState(MoveState.ARKHAM);
                            }
                        }
                    } // end of movestate == TRADE
                    else if (gameData.getMoveState().equals(MoveState.ACCEPTING_TRADE)
                            && sender.equals(gameData.getTradeRecipient().getPlayer()))
                    {
                        if (!cmd.equals("Accept Trade"))
                        {
                            return;
                        }
                        if (details.equals("Yes"))
                        {
                            List<InvestigatorCard> list1 = new ArrayList<InvestigatorCard>();
                            List<InvestigatorCard> list2 = new ArrayList<InvestigatorCard>();
                            Investigator inv = gameData.getCurrentInvestigator();
                            Investigator tinv = gameData.getTradeRecipient();
                            gameData.validateTrade(gameData.getTradeDetails(), inv, tinv,
                                    list1, list2);
                            gameData.tradeItems(inv, tinv, list1, list2);
                            CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                            addMessageCommand(cmsg, inv.getName() + " and " + tinv.getName() +
                                    " have " + "traded items.", gameData.getCurrentPlayer());
                            cmsg.addCommand("Items Traded", gameData.getTradeDetails());
                            server.serverCommandMessage(cmsg);
                            gameData.setMoveState(MoveState.ARKHAM);
                        }
                        else
                        {
                            gameData.setMoveState(MoveState.ARKHAM);
                            CommandMessage cmsg = new CommandMessage(-1);
                            addMessageCommand(cmsg, sender + " has declined the trade.", -1);
                            server.serverCommandMessage(cmsg);
                        }
                    } // end of movestate == ACCEPTING_TRADE
                    else if (sender.equals(gameData.getCurrentPlayerName()) &&
                            gameData.getMoveState().equals(MoveState.COMBAT))
                    {
                        if (cmd.equals("Fight Monster") || cmd.equals("Evade Monster"))
                        {
                            boolean fight = cmd.equals("Fight Monster");
                            if (!XmlTools.isNumeric(details))
                            {
                                AHLogger.error("serverCommandHandler", "Invalid monster id received" +
                                		" in start combat: " + details);
                                return;
                            }
                            int id = XmlTools.parseInt(details);
                            Monster mon = GameData.retrieveById(gameData.getCombatFrame(), id);
                            if (mon == null)
                            {
                                AHLogger.error("serverCommandHandler", "Invalid monster id received" +
                                        " in start combat: " + details);
                                return;
                            }
                            EventState ev1 = new EventState(mon, EventStateType.MOVE_MONSTER,
                                    gameData.getCurrentPlayer());
                            EventState ev2 = new EventState(mon, fight ?
                                    EventStateType.MONSTER_HORROR : EventStateType.MONSTER_EVADE,
                                    gameData.getCurrentPlayer());
                            ev2.setPrevState(ev1);
                            eventStack.push(ev2);
                            CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
                            resolveEvent(cmsg, null);
                            checkForUnresolvedEvents(cmsg);
                            if (cmsg.getCommands().size() > 0)
                            {
                                server.serverCommandMessage(cmsg);
                            }
                        }
                    } // end of movestate == COMBAT
                    break;
                case MYTHOS_MOVEMENT:
                    if (cmd.equals("Allocate Monster") &&
                            sender.equals(gameData.getPlayerName(gameData.getStartingPlayer())))
                    {
                        Pair<Monster, List<Investigator>> pair =
                            gameData.getUnassignedMonsters().remove(0);
                        Investigator inv = GameData.retrieveByName(pair.getB(), details);
                        gameData.getMonsterDestinations().put(pair.getA(), inv.getLocation());
                    }
                    break;
                case MYTHOS_EFFECT:

                    break;
                }
            }
        }
    }

    /**
     * The handler on the client side for game commands before game start
     * @param cmsg the command message
     * @param sender the sender
     */
    private void clientGameSetupHandler(CommandMessage cmsg, String sender)
    {
        /*
         * Client flow:
         * 1. After client sends initial ready (gameLoaded), server responds
         * with "Select Investigators" followed by a list of investigators
         * in the draft pool corresponding to the type of draft and selected
         * value.
         * 2. Each client in turn will first respond to "Select Investigators" with
         * the selected investigator's name. Server will broadcast each client's
         * response.
         * 3. After selecting an investigator, the client will send "Initialize
         * Sliders" with the investigator's slider positions. Server will broadcast
         * this and then turn moves to the next player.
         * 4. After all players have selected investigators and initialized sliders,
         * server will broadcast the selected ancient one. Clients will respond
         * with ready (aoSelect).
         * 5. The server will then broadcast "Receive Starting Items" for each
         * investigator in turn and then begin to execute an event loop to
         * distribute the starting items. In this step clients will respond as
         * if responding to events later in the game.
         */
        while (cmsg.getNumCommands() > 0)
        {
            Command command = cmsg.removeFirstCommand();
            String cmd = command.cmd;
            String details = command.details;
            if (cmd.startsWith("#"))
            {
                clientUpdateHandler(cmd, details, command.player);
                continue;
            }
            else if (cmd.startsWith("!"))
            {
                if (amOwner[command.player])
                {
                    setupActionPanel(cmd, details, command.player);
                }
                else
                {
                    viewManager.updateForNoAction("Awaiting decision", "Please wait while " +
                            gameData.getInvestigator(command.player).getName() +
                            " is making a decision");
                }
                continue;
            }
            else if (cmd.startsWith("*Roll"))
            {
                setupRollPanel(details, command.player);
                continue;
            }
            else if (cmd.startsWith("*Combat"))
            {
                setupCombatPanel(details, command.player);
                continue;
            }

            if (!gameData.isStarted())
            {
                switch (gameData.getGameSetupState())
                {
                case PRE_SETUP:
                    if (cmd.equals("Select Investigators"))
                    {
                        gameData.getInvestigatorDraftPool().clear();
                        String invs = details;
                        String[] tokens = invs.split(",");
                        for (int i = 0; i < tokens.length; i++)
                        {
                            Investigator inv = GameData.retrieveByName(gameData.getInvestigatorPool(),
                                    tokens[i]);
                            gameData.getInvestigatorDraftPool().add(inv);
                        }

                        gameData.resetTurnOrder(cmsg.getActivePlayer());
                        gameData.setGameSetupState(GameSetupState.SELECT_INVESTIGATORS);

                        if (activePlayer != -1)
                        {
                            updateActionForInvSelection();
                        }
                    }
                    break;
                case SELECT_INVESTIGATORS:
                    if (cmd.equals("Select Investigators"))
                    {
                        String[] tokens = details.split(",");
                        Investigator inv = GameData.retrieveByName(gameData.getInvestigatorPool(),
                                tokens[0]);
                        gameData.initializeInvestigatorWithoutItems(inv);
                        for (int i = 1; i < tokens.length; i++)
                        {
                            InvestigatorCard card = gameData.lookupCardArray(XmlTools.parseInt(tokens[i]));
                            inv.gainInvestigatorCard(card);
                            gameData.removeFromDeck(card);
                        }
                        gameData.assignInvestigator(gameData.getCurrentPlayer(), inv);
                        displayMetaInfo(gameData.getCurrentPlayerName(), gameData.getCurrentPlayerName() +
                                " has selected " + inv.getName());
                        if (amOwner[gameData.getCurrentPlayer()])
                        {
                            viewManager.updateForSliderAdjustment(inv, true);
                        }
                        synchronized(actionLock)
                        {
                            suspendAction = false;
                        }
                    }
                    else if (cmd.equals("Initialize Sliders"))
                    {
                        Investigator inv = gameData.getCurrentInvestigator();
                        String[] tokens = details.split(",");
                        inv.setSkillSliders(tokens);
                        boolean complete = gameData.advanceTurnOrder();
                        if (!complete)
                        {
                            updateActionForInvSelection();
                        }
                        else
                        {
                            gameData.resetTurnOrder(gameData.getStartingPlayer());
                            gameData.setGameSetupState(GameSetupState.SELECT_ANCIENT_ONE);
                            viewManager.getSlidingPanel().setExtended(false);
                            viewManager.updateForNoAction("Select Ancient One",
                                    "Please wait while the Ancient One is selected.");
                        }
                        synchronized(actionLock)
                        {
                            suspendAction = false;
                            nextActivePlayer();
                        }
                    }
                    break;
                case SELECT_ANCIENT_ONE:
                    if (cmd.equals("Select Ancient One"))
                    {
                        AncientOne ao = GameData.retrieveByName(gameData.getAOPool(), details);
                        gameData.setAncientOne(ao);
                        CommandMessage c2 = new CommandMessage("ready", "aoSelect");
                        client.sendCommandMessage(c2);
                        chatManager.displayMessage("# " + ao.getName() +
                            " is selected as the Ancient One.");
                    }
                    else
                    {
                        if (cmd.equals("Receive Starting Items"))
                        {
                            gameData.setGameSetupState(GameSetupState.RECEIVE_STARTING_ITEMS);
                            viewManager.updateForNoAction("Starting Items", "Please wait while the other" +
                            " investigators receive their starting items.");
                        }
                    }
                    break;
                case RECEIVE_STARTING_ITEMS:
                    if (cmd.equals("Receive Starting Items"))
                    {
                        gameData.advanceTurnOrder();
                        synchronized(actionLock)
                        {
                            if (suspendAction)
                            {
                                suspendAction = false;
                                nextActivePlayer();
                            }
                        }
                        viewManager.updateForNoAction("Starting Items", "Please wait while the other" +
                        " investigators receive their starting items.");
                    }
                    else if (cmd.equals("ready") && details.equals("firstMythos"))
                    {
                        gameData.resetTurnOrder(gameData.getStartingPlayer());
                        gameData.setGameSetupState(GameSetupState.FIRST_MYTHOS);
                        CommandMessage c2 = new CommandMessage("ready", "firstMythos");
                        client.sendCommandMessage(c2);
                        viewManager.updateForNoAction("Mythos", "Please wait while a mythos card " +
                                "is drawn.");
                    }
                    break;
                case FIRST_MYTHOS:
                    if (cmd.equals("Mythos Gate"))
                    {
                        gameData.startGame();
                        gameData.setGamePhase(GamePhase.MYTHOS_GATE);
                        gameData.drawMythosCard(details);
                        viewManager.getInfoDisplay().displayMythosCard(gameData.getLastDrawnMythos());
                    }
                    break;
                }
            } // end of game is not started
            else
            {
                switch (gameData.getGamePhase())
                {
                case PRE_UPKEEP:
                    if (cmd.equals("Pre-Upkeep") && details.equals("done"))
                    {
                        gameData.setGamePhase(GamePhase.UPKEEP);
                        for (Investigator inv : gameData.getInvestigators())
                        {
                            inv.setSkillSliderFlag(false);
                        }
                    }
                    break;
                case UPKEEP:
                    if (cmd.equals("Set Sliders"))
                    {
                        gameData.resetTurnOrder(command.player);
                        updateActionForUpkeep(amOwner[command.player]);
                    }
                    else if (cmd.equals("Slider Positions"))
                    {
                        Investigator inv = gameData.getInvestigator(command.player);
                        String[] tokens = details.split(",");
                        inv.setSkillSliders(tokens);
                    }
                    else if (cmd.equals("Use Effects"))
                    {
                        gameData.setGamePhase(GamePhase.UPKEEP_EFFECT);
                        updateActionForUpkeepEffects(amOwner[command.player]);
                    }
                    else if (cmd.equals("Upkeep") && details.equals("done"))
                    {
                        gameData.resetTurnOrder(command.player);
                        gameData.setGamePhase(GamePhase.MOVEMENT);
                        for (Investigator inv : gameData.getInvestigators())
                        {
                            gameData.prepareForMovement(inv);
                        }
                    }
                    break;
                case UPKEEP_EFFECT:
                    if (cmd.equals("Set Sliders"))
                    {
                        gameData.resetTurnOrder(command.player);
                        gameData.setGamePhase(GamePhase.UPKEEP);
                        updateActionForUpkeep(amOwner[command.player]);
                    }
                    else if (cmd.equals("Use Effects"))
                    {
                        updateActionForUpkeepEffects(amOwner[command.player]);
                    }
                    else if (cmd.equals("Upkeep") && details.equals("done"))
                    {
                        gameData.resetTurnOrder(command.player);
                        gameData.setGamePhase(GamePhase.MOVEMENT);
                        for (Investigator inv : gameData.getInvestigators())
                        {
                            gameData.prepareForMovement(inv);
                        }
                    }
                    break;
                case MOVEMENT:
                    if (cmd.equals("Do Movement"))
                    {
                        gameData.resetTurnOrder(command.player);
                        gameData.setMoveState(MoveState.ARKHAM);
                        updateActionForMovement(amOwner[command.player]);
                    }
                    else if (cmd.equals("End Combat"))
                    {
                        Investigator inv = gameData.getCurrentInvestigator();
                        inv.setAvoidedMonsters(true);
                        gameData.setMoveState(MoveState.ARKHAM);
                    }
                    else if (cmd.equals("Move To"))
                    {
                        String[] tokens = details.split(",");
                        Investigator inv = gameData.getCurrentInvestigator();
                        LocationContainer loc = gameData.getLocationContainer(tokens[0]);
                        gameData.moveInvestigatorTo(inv, loc);
                        inv.setAvoidedMonsters(false);
                        inv.useMovementPoints(XmlTools.parseInt(tokens[1]));
                    }
                    else if (cmd.equals("End Movement"))
                    {
                        String[] tokens = details.split(",");
                        Investigator inv = gameData.getCurrentInvestigator();
                        LocationContainer loc = gameData.getLocationContainer(tokens[0]);
                        gameData.moveInvestigatorTo(inv, loc);
                        inv.setDelayed(Boolean.parseBoolean(tokens[1]));
                    }
                    else if (cmd.equals("Trade Items"))
                    {
                        Investigator tinv = gameData.getInvestigatorByName(details);
                        if (tinv != null)
                        {
                            gameData.setMoveState(MoveState.TRADE);
                            gameData.setTradeRecipient(tinv);
                            updateActionForTrade(amOwner[gameData.getCurrentPlayer()]);
                        }
                    }
                    else if (cmd.equals("Accept Trade"))
                    {
                        gameData.setTradeDetails(details);
                        gameData.setMoveState(MoveState.ACCEPTING_TRADE);
                        updateActionForTradeAccept(amOwner[command.player]);
                    }
                    else if (cmd.equals("Items Traded"))
                    {
                        List<InvestigatorCard> list1 = new ArrayList<InvestigatorCard>();
                        List<InvestigatorCard> list2 = new ArrayList<InvestigatorCard>();
                        Investigator inv = gameData.getCurrentInvestigator();
                        Investigator tinv = gameData.getTradeRecipient();
                        gameData.validateTrade(gameData.getTradeDetails(), inv, tinv,
                                list1, list2);
                        gameData.tradeItems(inv, tinv, list1, list2);
                        gameData.setMoveState(MoveState.ARKHAM);
                    }
                    else if (cmd.equals("Start Combat"))
                    {
                        gameData.updateCombatFrame(details);
                        gameData.setMoveState(MoveState.COMBAT);
                        updateActionForStartCombat(amOwner[command.player]);
                    }
                    else if (cmd.equals("Movement") && details.equals("done"))
                    {
                        gameData.setGamePhase(GamePhase.ARKHAM_ENCOUNTER);
                        gameData.clearCurrentEvent();
                        gameData.resetTurnOrder(command.player);
                    }
                    break;
                case ARKHAM_ENCOUNTER:
                    if (cmd.equals("Arkham Encounter") && details.equals("done"))
                    {
                        gameData.clearCurrentEvent();
                        boolean res = gameData.advanceTurnOrder();
                        if (res)
                        {
                            gameData.setGamePhase(GamePhase.OTHER_WORLD_ENCOUNTER);
                        }
                    }
                    break;
                case OTHER_WORLD_ENCOUNTER:
                    if (cmd.equals("Other World Encounter") && details.equals("done"))
                    {
                        gameData.clearCurrentEvent();
                        boolean res = gameData.advanceTurnOrder();

                    }
                    break;
                case MYTHOS_GATE:
                    MythosCard mythos = gameData.getLastDrawnMythos();
                    if (cmd.equals("Mythos Gate Open"))
                    {
                        String[] tokens = details.split(",");
                        gameData.openGateOnLocation(tokens[0], mythos.getGateLocation());
                        for (int i = 1; i < tokens.length; i++)
                        {
                            gameData.spawnMonsterOnLocation(tokens[i], mythos.getGateLocation());
                        }
                        gameData.setGamePhase(GamePhase.MYTHOS_CLUE);
                    }
                    else if (cmd.equals("Mythos Gate Surge"))
                    {
                        String[] tokens = details.split(",");
                        for (int i = 0; i < tokens.length; i += 2)
                        {
                            gameData.spawnMonsterOnLocation(tokens[i + 1], tokens[i]);
                        }
                        gameData.setGamePhase(GamePhase.MYTHOS_CLUE);
                    }
                    else if (cmd.equals("Mythos Gate Sealed"))
                    {
                        // nothing happens here, phew
                        gameData.setGamePhase(GamePhase.MYTHOS_CLUE);
                    }
                    break;
                case MYTHOS_CLUE:
                    if (cmd.equals("Mythos Clue") && details.equals("done"))
                    {
                        gameData.setGamePhase(GamePhase.MYTHOS_MOVEMENT);
                    }
                    break;
                case MYTHOS_MOVEMENT:
                    if (cmd.equals("Allocate Monster") && amOwner[command.player])
                    {
                        setupActionPanel("Allocate Monster", details, command.player);
                    }
                    else if (cmd.equals("Mythos Movement"))
                    {
                        gameData.moveMonsters(details);
                        gameData.setGamePhase(GamePhase.MYTHOS_SPECIAL);
                    }
                    break;
                case MYTHOS_SPECIAL:
                    if (cmd.equals("Mythos Special") && details.equals("done"))
                    {
                        gameData.setGamePhase(GamePhase.MYTHOS_EFFECT);
                    }
                    break;
                case MYTHOS_EFFECT:
                    if (cmd.equals("Mythos Effect") && details.equals("done"))
                    {
                        gameData.setGamePhase(GamePhase.PRE_UPKEEP);
                        gameData.advanceGameTurn();
                        if (gameData.getGameTurn() > 1)
                        {
                            gameData.advanceStartingPlayer();
                        }
                        gameData.resetTurnOrder(gameData.getStartingPlayer());
                        gameData.refreshInvestigators();
                    }
                    break;
                } // end switch
            } // end of else (!game is started)
        } // end while

    }

    /**
     * Handles # commands to the client, which performs an update of game state
     * @param cmd the command
     * @param details the details of the command
     * @param player the affected player
     */
    private void clientUpdateHandler(String cmd, String details, int player)
    {
        Investigator inv = gameData.getInvestigator(player);
        if (cmd.equals("#Invcard"))
        {
            // investigator receives investigator cards
            String[] tokens = details.split(",");
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < tokens.length; i++)
            {
                InvestigatorCard card = gameData.lookupCardArray(XmlTools.parseInt(tokens[i]));
                inv.gainInvestigatorCard(card);
                gameData.removeFromDeck(card);
                if (sb.length() != 0)
                {
                    sb.append(", ");
                }
                sb.append(card.getName());
            }
            displayMetaInfo(inv.getPlayer(), inv.getName() + " has received: " +
                    sb.toString());
        }
        else if (cmd.equals("#LoseInvcard"))
        {
            String[] tokens = details.split(",");
            for (String tok : tokens)
            {
                InvestigatorCard card = inv.loseInvestigatorCard(XmlTools.parseInt(tok));
                if (card != null)
                {
                    gameData.returnToDeck(card);
                }
            }
        }
        else if (cmd.equals("#AddClue"))
        {
            int val = XmlTools.parseInt(details);
            inv.modifyClues(val);
        }
        else if (cmd.equals("#AddMoney"))
        {
            int val = XmlTools.parseInt(details);
            inv.modifyMoney(val);
        }
        else if (cmd.equals("#GainStatus"))
        {
            inv.applyStatus(gameData.createPersonalEffect(details));
        }
        else if (cmd.equals("#LoseStatus"))
        {
            inv.removeStatus(details);
        }
        else if (cmd.equals("#Message"))
        {
            // just displays a message
            if (details.startsWith(">>") || inv == null)
            {
                chatManager.displayMessage(details);
            }
            else
            {
                displayMetaInfo(inv.getPlayer(), details);
            }
        }
        else if (cmd.equals("#AddDoom"))
        {
            gameData.getAncientOne().modifyDoomTokens(XmlTools.parseInt(details));
        }
        else if (cmd.equals("#AddTerror"))
        {
            gameData.raiseTerrorLevel(XmlTools.parseInt(details));
        }
        else if (cmd.equals("#LocationClue"))
        {
            Location loc = gameData.getLocation(details);
            loc.addClue();
        }
        else if (cmd.equals("#LocationClueless"))
        {
            Location loc = gameData.getLocation(details);
            loc.setClues(0);
        }
        else if (cmd.equals("#Damage"))
        {
            String[] tokens = details.split(",");
            if (tokens[0].equals("Stamina"))
            {
                int damage = XmlTools.parseInt(tokens[1]);
                inv.modifyStamina(-damage);
                displayMetaInfo(inv.getPlayer(), inv.getName() + " lost " + damage + " Stamina.");
            }
            else if (tokens[0].equals("Sanity"))
            {
                int damage = XmlTools.parseInt(tokens[1]);
                inv.modifySanity(-damage);
                displayMetaInfo(inv.getPlayer(), inv.getName() + " lost " + damage + " Sanity.");
            }
        }
        else if (cmd.equals("#Heal"))
        {
            String[] tokens = details.split(",");
            if (tokens[0].equals("Stamina"))
            {
                int healAmt = XmlTools.parseInt(tokens[1]);
                inv.modifyStamina(healAmt);
            }
            else if (tokens[0].equals("Sanity"))
            {
                int healAmt = XmlTools.parseInt(tokens[1]);
                inv.modifySanity(healAmt);
            }
        }
        else if (cmd.equals("#SetStamina"))
        {
            inv.setStamina(XmlTools.parseInt(details));
        }
        else if (cmd.equals("#SetSanity"))
        {
            inv.setSanity(XmlTools.parseInt(details));
        }
        else if (cmd.equals("#Teleport"))
        {
            gameData.moveInvestigatorTo(inv, gameData.getLocationContainer(details));
        }
        else if (cmd.equals("#Instant"))
        {
            if (details.equals("Delayed"))
            {
                inv.setDelayed(true);
            }
        }
        else if (cmd.equals("#DefeatMonster"))
        {
            String[] tokens = details.split(",");
            int id = XmlTools.parseInt(tokens[0]);
            Monster mon = gameData.getMonsterById(id);
            if (mon.getLocation() != null)
            {
                GameData.removeById(mon.getLocation().getMonsterList(), id);
            }
            if (tokens[1].equals("true"))
            {
                inv.getMonsterTrophies().add(mon);
            }
        }
        else if (cmd.equals("#Recharge"))
        {
            inv.setMovementPoints(inv.getMovementPoints() + XmlTools.parseInt(details));
        }
        else if (cmd.equals("#Consume"))
        {
            gameData.consumeModEffect(inv, details);
        }
        else if (cmd.equals("#UConsume"))
        {
            gameData.consumeUsableEffect(inv, details);
        }
        else if (cmd.equals("#ExhaustEffect"))
        {
            EffectSource effSrc = inv.searchEffectSource(details);
            effSrc.setExhausted(true);
        }
        else if (cmd.equals("#AddToken"))
        {
            EffectSource effSrc = inv.searchEffectSource(details);
            gameData.addTokensToEffectSource(inv, effSrc, 1);
        }
        else if (cmd.equals("#Encounter"))
        {
            String[] tokens = details.split(",");
            EventNode en = gameData.drawLocationCard(tokens[0], tokens[1]);
            gameData.setCurrentEvent(en);
        }
        else if (cmd.equals("#OWEncounter"))
        {
            String[] tokens = details.split(",");
            GateCard card = gameData.getGateCardByName(tokens[1]);
            gameData.setCurrentEvent(card.getOtherWorldEncounter(tokens[0]));
        }
        else if (cmd.equals("#SpawnMonster"))
        {
            String[] tokens = details.split(",");
            for (int i = 1; i < tokens.length; i++)
            {
                gameData.spawnMonsterOnLocation(tokens[i], tokens[0]);
            }
        }
        else if (cmd.equals("#ReturnMonsters"))
        {
            gameData.returnMonstersOn(details);
        }
        else if (cmd.equals("#RampagingMonsters"))
        {
            gameData.returnMonstersNamed(details);
        }
        else if (cmd.equals("#NewEnvironment"))
        {
            Environment env = gameData.getEnvironment(details);
            env.initialize();
            gameData.getActiveEnvironments().add(env);
        }
        else if (cmd.equals("#NewRumor"))
        {
            Rumor r = gameData.getRumor(details);
            r.init(gameData.getNumPlayers());
            gameData.setActiveRumor(r);
        }
    }

    /**
     * Starts the investigator selection process. This method
     * is called by the server.
     */
    private void startInvestigatorSelection()
    {
        // randomly select and distribute investigators to each owner
        gameData.draftInvestigators();
        StringBuffer sb = new StringBuffer();
        List<Investigator> invlist = gameData.getInvestigatorDraftPool();
        for (int i = 0; i < invlist.size(); i++)
        {
            if (i != 0) sb.append(",");
            sb.append(invlist.get(i).getName());
        }
        CommandMessage cmsg = new CommandMessage(0);
        cmsg.addCommand("Select Investigators", sb.toString());
        viewManager.updateTurnOrder(gameData.getCurrentPlayer(), activePlayer);

        if (activePlayer != -1)
        {
            updateActionForInvSelection();
        }
        server.serverCommandMessage(cmsg);
    }

    /**
     * Distributes the items for an investigator
     * @param index the index of the investigator
     */
    private void distributeRandomItems(CommandMessage cmsg, int index)
    {
        AHLogger.info("Distributing random items for player " + index);
        Investigator inv = gameData.getInvestigator(index);
        EventNode node = EventUtils.createTypeSequence("Investigator Gains",
                inv.getRandomPossessions());
        EventState es = new EventState(node, EventStateType.NEW_EVENT, index);
        eventStack.push(es);
    }

    /**
     * Draws a mythos card, and populates command message
     * for sending back to the clients.
     * @param cmsg the command message to populate
     */
    private void drawMythosGate(CommandMessage cmsg)
    {
        /* Steps:
         * 1. draw the mythos card, putting the previous one below the deck
         * 2. check the gate location and determine if it is a gate, monster
         * surge, or no gate
         * 3. If it is a gate, send the #GateOpen message
         * 4. If it is a monster surge, send the #MonsterSurge message
         * 5. Doom or terror will raise automatically as a result of gate opening,
         * effects that respond to it will be triggered after gate and monsters
         * are placed on the board
         */
        MythosCard mythos = gameData.drawMythosCard();
        Location loc = gameData.getLocation(mythos.getGateLocation());
        StringBuffer sb = new StringBuffer();
        cmsg.addCommand("Mythos Gate", mythos.getName());
        if (loc.isSealed())
        {
            // if location is sealed or has Kate Winthrop on it
            addMessageCommand(cmsg, "The seal on " + loc.getName() + " prevented a " +
                    "gate from opening.", -1);
            cmsg.addCommand("Mythos Gate Sealed", "");
        }
        else if (gameData.locationCheck(loc, "Investigator Special", "Science!"))
        {
            sb.append("Sealed");
            addMessageCommand(cmsg, "Kate Winthrop's Science! ability prevented a " +
                    "gate from opening on " + loc.getName(), -1);
            cmsg.addCommand("Mythos Gate Sealed", "");
        }
        else if (loc.hasOpenGate())
        {
            /* monster surge, decide where to put the monsters. Check if monsters can
             * be evenly divided
             */
            int openGates = gameData.getNumOpenGates();
            int numMonsters = Math.max(openGates, gameData.getNumPlayers());
            EventNode en = EventUtils.createEffectNode("Mythos Gate Surge", loc.getName(),
                    String.valueOf(numMonsters));
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT));
        }
        else
        {
            // normal gate opening
            GateTile gate = gameData.openRandomGateOn(loc);
            EventNode en = EventUtils.createEffectNode("Global Variable Modification", "Doom", "1");
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT));
            sb.append(gate.getName());
            // draw 1 or 2 monsters
            for (int i = 0; i < ((gameData.getNumPlayers() + 3)/ 4); i++)
            {
                Monster mon = spawnMonsterOn(cmsg, loc);
                sb.append("," + mon.getId());
            }
            addMessageCommand(cmsg, "A gate to " + gate.getName() + " opens on " +
                    loc.getName() + ". Doom rises to " +
                    (gameData.getAncientOne().getDoomTokens() + 1), -1);
            cmsg.addCommand("Mythos Gate Open", sb.toString());
            //TODO: mythos gate opening on player
        }
    }

    /**
     * Resolves the clue placement step in the mythos resolution
     * @param cmsg the command message to populate
     */
    private void doMythosClue(CommandMessage cmsg)
    {
        MythosCard mythos = gameData.getLastDrawnMythos();
        int size = mythos.getClueLocation().size();
        if (size == 1)
        {
            Location loc = gameData.getLocation(mythos.getClueLocation().get(0));
            EventNode en = EventUtils.createEffectNode("Location Clue", loc.getName(), "1");
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT,
                    gameData.getStartingPlayer()));
        }
        else if (size > 1)
        {
            List<Pair<String, String>> list = new ArrayList<Pair<String, String>>();
            for (String loc : mythos.getClueLocation())
            {
                list.add(new Pair<String, String>(loc, "1"));
            }
            EventNode en = EventUtils.createTypeSequence("Location Clue", list);
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT,
                    gameData.getStartingPlayer()));
        }
    }

    /**
     * Resolves the movement of special movement monsters in the mythos
     * phase.
     * @param cmsg the command message to populate
     */
    private void doSpecialMovement(CommandMessage cmsg)
    {
        for (Monster mon : gameData.getSpecialMonsters())
        {
            String type = mon.getSpecialMovementEffect();
            int die = gameData.rollOneDie();
            if (die < mon.getSpecialMovementDie())
            {
                addMessageCommand(cmsg, "Special movement for " + mon.getName() +
                    " is not triggered as the die roll is " + die, -1);
                continue;
            }
            addMessageCommand(cmsg, "Special movement for " + mon.getName() +
                    " is triggered as the die roll is " + die, -1);
            int numInvs = gameData.getNumPlayers();
            if (type.equals("Arkham Damage Stamina"))
            {
                for (int i = numInvs - 1; i >= 0; i--)
                {
                    Investigator inv = gameData.getInvestigator(i);
                    if (inv.getLocation().isArkhamLocation())
                    {
                        EventNode en = EventUtils.createEffectNode("Damage", "Stamina", "1");
                        EventState es = new EventState(en, EventStateType.NEW_EVENT, inv.getIndex());
                        eventStack.push(es);
                    }
                }
            }
            else if (type.equals("Arkham Damage Sanity"))
            {
                for (int i = numInvs - 1; i >= 0; i--)
                {
                    Investigator inv = gameData.getInvestigator(i);
                    if (inv.getLocation().isArkhamLocation())
                    {
                        EventNode en = EventUtils.createEffectNode("Damage", "Sanity", "1");
                        EventState es = new EventState(en, EventStateType.NEW_EVENT, inv.getIndex());
                        eventStack.push(es);
                    }
                }
            }
        }
    }

    /**
     * Resolves the mythos effect stage where environments, headlines and rumors
     * come into play.
     * @param cmsg the command message
     */
    private void doMythosEffect(CommandMessage cmsg)
    {
        MythosCard mythos = gameData.getLastDrawnMythos();
        /* Remove old environments */
        if (mythos.getType().isEnvironment())
        {
            gameData.renewEnvironment();
        }
        for (Pair<String, String> mythEff : mythos.getEffects())
        {
            if (mythEff.getA().equals("Headline"))
            {
                addMessageCommand(cmsg, "Headline: " + mythos.getDescription(), -1);
                EventNode en = gameData.getHeadline(mythEff.getB());
                if (en.getType().equalsIgnoreCase("Each Investigator"))
                {
                    for (int i = gameData.getNumPlayers() - 1; i >= 0; i--)
                    {
                        EventState es = new EventState(en, EventStateType.NEW_EVENT, i);
                        eventStack.push(es);
                    }
                }
                else if (en.getType().equalsIgnoreCase("Each Investigator in Arkham"))
                {
                    for (int i = gameData.getNumPlayers() - 1; i >= 0; i--)
                    {
                        Investigator inv = gameData.getInvestigator(i);
                        if (!inv.getLocation().isArkhamLocation())
                        {
                            continue;
                        }
                        EventState es = new EventState(en, EventStateType.NEW_EVENT, i);
                        eventStack.push(es);
                    }
                }
                else if (en.getType().equalsIgnoreCase("First Investigator"))
                {
                    EventState es = new EventState(en, EventStateType.NEW_EVENT,
                            gameData.getStartingPlayer());
                    eventStack.push(es);
                }
                else if (en.getType().equalsIgnoreCase("Global"))
                {
                    EventState es = new EventState(en, EventStateType.NEW_EVENT);
                    eventStack.push(es);
                }
            }
            else if (mythEff.getA().equals("LocalEvent"))
            {
                EventNode en = gameData.getLocalEvent(mythEff.getB());
                gameData.addLocalEvent(en);
                /* There is no need to send a command message for local events */
            }
            else if (mythEff.getA().equals("Environment"))
            {
                Environment env = gameData.getEnvironment(mythEff.getB());
                env.initialize();
                gameData.getActiveEnvironments().add(env);
                cmsg.addCommand("#NewEnvironment", mythEff.getB());
            }
            else if (mythEff.getA().equals("Rumor"))
            {
                Rumor r = gameData.getRumor(mythEff.getB());
                r.init(gameData.getNumPlayers());
                gameData.setActiveRumor(r);
                cmsg.addCommand("#NewRumor", mythEff.getB());
            }
        }
    }

    /**
     * Handles the pre-upkeep phase, where the server checks for effects that expire
     * during upkeep.
     * @param cmsg the command message
     */
    private void doPreUpkeep(CommandMessage cmsg)
    {
        /* 1. Execute pre-upkeep usable effects */
        for (int i = numPlayers - 1; i >= 0; i--)
        {
            int index = (i + gameData.getStartingPlayer()) % numPlayers;
            Investigator inv = gameData.getInvestigator(index);
            List<UsableEffect> list = inv.searchAutoUsableEffects(UsablePhase.PRE_UPKEEP);
            for (UsableEffect ueff : list)
            {
                EventState es = new EventState(ueff.getEvent(), EventStateType.NEW_EVENT, index);
                eventStack.push(es);
            }
            /* 2. Check for expiry of personal effects */
            for (int j = 0; j < inv.getStatusEffects().size(); j++)
            {
                PersonalEffect peff = inv.getStatusEffects().get(j);
                if (!peff.hasProperty("Expires"))
                {
                    continue;
                }
                String expiry = peff.getProperty("Expires");
                if (expiry.equals("Single Turn"))
                {
                    inv.removeStatus(peff);
                    cmsg.addCommand("#LoseStatus", peff.getName());
                    displayMetaInfo(inv.getPlayer(), inv.getName() + " has lost " +
                            peff.getName() + ".");
                    j--;
                }
                else if (expiry.equals("Upkeep Roll"))
                {
                    /* Don't roll if it is the first turn the effect is in play */
                    if (peff.isFirstTurn())
                    {
                        peff.advanceTurn();
                        continue;
                    }
                    int die = gameData.rollOneDie();
                    if (die == 1)
                    {
                        inv.removeStatus(peff);
                        cmsg.addCommand("#LoseStatus", peff.getName());
                        addMessageCommand(cmsg, "Upkeep roll for " + inv.getName() + "'s " +
                                peff.getName() + ": 1! Effect is lost." , index);
                        j--;
                    }
                    else
                    {
                        addMessageCommand(cmsg, "Upkeep roll for " + inv.getName() + "'s " +
                                peff.getName() + ": " + die, index);
                    }
                }
            }
        }
    }

    /**
     * Spawns a random monster on a location. Handles side effects such as
     * sending to outskirts or terror level rising
     * @param cmsg the command message
     * @return the monster spawned
     */
    private Monster spawnMonsterOn(CommandMessage cmsg, Location loc)
    {
        Monster mon = gameData.drawRandomMonster();
        if (gameData.atMonsterLimit())
        {
            LocationContainer outskirts = gameData.getLocationContainer(Board.OUTSKIRTS_NAME);
            outskirts.getMonsterList().add(mon);
            if (outskirts.getNumMonsters() > gameData.getOutskirtsLimit())
            {
                gameData.getMonsters().addAll(outskirts.getMonsterList());
                outskirts.getMonsterList().clear();
                addMessageCommand(cmsg, "The terror level has risen because the " +
                        "outskirts are full of monsters!", -1);
                EventNode en = EventUtils.createEffectNode("Global Variable Modification", "Terror", "1");
                eventStack.push(new EventState(en, EventStateType.NEW_EVENT));
            }
        }
        else
        {
            gameData.moveMonsterTo(mon, loc);
        }
        return mon;
    }

    /**
     * Checks for unresolved events in the command message and whether the
     * server is the owner of any of these events
     * @param cmsg the command message
     */
    private void checkForUnresolvedEvents(CommandMessage cmsg)
    {
        if (eventStack.isEmpty())
        {
            // no more unresolved events, send done message
            viewManager.updateDisplay();
        }
        else
        {
            // have unresolved events, check if self is owner
            Command activeCommand = null;
            Command passiveCommand = null;
            for (Command command : cmsg.getCommands())
            {
                if (command.cmd.startsWith("!"))
                {
                    if (amOwner[command.player])
                    {
                        activeCommand = command;
                    }
                    else
                    {
                        passiveCommand = command;
                    }
                    break;
                }
                if (command.cmd.startsWith("*Roll"))
                {
                    setupRollPanel(command.details, command.player);
                    return;
                }
                if (command.cmd.startsWith("*Combat"))
                {
                    setupCombatPanel(command.details, command.player);
                    return;
                }
            }
            if (activeCommand != null)
            {
                setupActionPanel(activeCommand.cmd, activeCommand.details, activeCommand.player);
            }
            else if (passiveCommand != null)
            {
                viewManager.updateForNoAction("Awaiting decision", "Please wait while " +
                        gameData.getInvestigator(passiveCommand.player).getName() +
                        " is making a decision");
            }
        }
    }

    /**
     * Called as the server to resolve the result of an event.
     * @param command the command string input, if any
     */
    @SuppressWarnings("unchecked")
    private void resolveEvent(CommandMessage cmsg, Pair<String, String> command)
    {
        while (!eventStack.isEmpty())
        {
            EventState es = eventStack.pop();
            if (es.getMessage() != null)
            {
                addMessageCommand(cmsg, es.getMessage(), es.getPlayer());
                es.setMessage(null);
            }

            switch (es.getType())
            {
            case NEW_EVENT:
                /* Main event handling code */
                EventNode event = es.getNode();
                if (event.getEffect() != null)
                {
                    EventState efs = new EventState(event.getEffect(),
                            EventStateType.EFFECT_DECISION, es.getPlayer());
                    boolean res = resolveEffect(efs, cmsg);
                    if (res)
                    {
                        return;
                    }
                }
                else if (event.getRoll() != null)
                {
                    es.setType(EventStateType.EVENT_ROLL);
                    es.setRoll(event.getRoll());
                    es.clearProperty("RollResult");
                    eventStack.push(es);
                    continue;
                }
                else if (event.getChoice() != null)
                {
                    es.setType(EventStateType.EVENT_CHOICE);
                    command = null;
                    eventStack.push(es);
                    break;
                }
                else if (event.getItemcheck() != null)
                {
                    Investigator inv = gameData.getInvestigator(es.getPlayer());
                    if (gameData.makeItemcheck(inv, event.getItemcheck().getCost(),
                            event.getItemcheck().getCost()))
                    {
                        if (event.getItempass() != null)
                        {
                            EventState evstate = new EventState(event.getItempass(),
                                    EventStateType.NEW_EVENT, es.getPlayer());
                            eventStack.push(evstate);
                        }
                    }
                    else
                    {
                        if (event.getItemfail() != null)
                        {
                            EventState evstate = new EventState(event.getItemfail(),
                                    EventStateType.NEW_EVENT, es.getPlayer());
                            eventStack.push(evstate);
                        }
                    }
                    break;
                }
                else if (event.getSequence().size() > 0)
                {
                    EventUtils.sortSequence(event);
                    for (int i = event.getSequence().size() - 1; i >= 0; i--)
                    {
                        /* Inherit properties from event */
                        EventState evstate = new EventState(event.getSequence().get(i),
                                EventStateType.NEW_EVENT, es.getPlayer());
                        evstate.inheritProperties(es);
                        eventStack.push(evstate);
                    }
                }
                else
                {

                }
                break;
            case EVENT_ROLL:
                if (es.isDefined("RollResult"))
                {
                    int successes = 0;
                    String rollResult = (String)es.getProperty("RollResult");
                    event = es.getNode();
                    RollType roll = es.getRoll();
                    if (rollResult.equals("Autopass"))
                    {
                        /* For auto-passes, it counts as 1 success in the case
                         * of multiple success types. Otherwise, execute the
                         * roll pass event
                         */
                        if (event.getRollcount().size() > 0)
                        {
                            successes = 1;
                        }
                        else
                        {
                            if (event.getRollpass() != null)
                            {
                                EventState evstate = new EventState(event.getRollpass(),
                                        EventStateType.NEW_EVENT, es.getPlayer());
                                eventStack.push(evstate);
                            }
                            break;
                        }
                    }
                    else if (rollResult.equals("Dice"))
                    {
                        Investigator inv = gameData.getInvestigator(es.getPlayer());
                        successes = countSuccesses(inv, roll.getType(), roll.getModifier(),
                                es.getDice(), event.getRollcount().size() == 0);
                    }
                    /* Finalize result */
                    if (event.getRollcount().size() > 0)
                    {
                        EventUtils.sortRollCount(event);
                        for (int i = 0; i < event.getRollcount().size(); i++)
                        {
                            if (event.getRollcount().get(i).getValue() <= successes)
                            {
                                EventState evstate = new EventState(event.getRollcount().get(i),
                                        EventStateType.NEW_EVENT, es.getPlayer());
                                eventStack.push(evstate);
                                break;
                            }
                        }
                    }
                    else
                    {
                        if ((roll.getDifficulty() == null && successes >= 1) ||
                                (roll.getDifficulty() != null && successes >= roll.getDifficulty()))

                        {
                            if (event.getRollpass() != null)
                            {
                                EventState evstate = new EventState(event.getRollpass(),
                                        EventStateType.NEW_EVENT, es.getPlayer());
                                eventStack.push(evstate);
                                break;
                            }
                        }
                        else
                        {
                            if (event.getRollfail() != null)
                            {
                                EventState evstate = new EventState(event.getRollfail(),
                                        EventStateType.NEW_EVENT, es.getPlayer());
                                eventStack.push(evstate);
                                break;
                            }
                        }
                    }
                }
                else
                {
                    EventState evstate = new EventState(es.getRoll(), EventStateType.ROLL_BEFORE,
                            es.getPlayer());
                    evstate.setPrevState(es);
                    eventStack.push(evstate);
                }
                break;
            case SPELL_ACTIVATION:
            {
                if (es.isDefined("RollResult"))
                {
                    String rollResult = (String)es.getProperty("RollResult");
                    int successes = 0, difficulty = 1;
                    if (es.getSpell().hasProperty("SpecialDifficulty") &&
                            es.isDefined("SpecialDifficulty"))
                    {
                        difficulty = (Integer)es.getProperty("SpecialDifficulty");
                    }
                    if (rollResult.equals("Dice"))
                    {
                        Investigator inv = gameData.getInvestigator(es.getPlayer());
                        successes = countSuccesses(inv, "Spell", 5, es.getDice(), true);
                    }
                    else if (rollResult.equals("Autopass"))
                    {
                        successes = difficulty;
                    }
                    EventState prevState = es.getPrevState();
                    prevState.addProperty("CastResult", new Boolean(successes >= difficulty));
                    prevState.addProperty("RollSuccesses", new Integer(successes));
                    AHLogger.info("resolveSpellActivation", "Casting result is " +
                            prevState.getProperty("CastResult"));
                    eventStack.push(prevState);
                    break;
                }
                Spell spell = es.getSpell();
                RollType roll = EventUtils.createRollType("Spell", spell.getCastingModifier(), 1);
                if (spell.getCastingModifier() == Globals.SPECIAL_MODIFIER &&
                        es.isDefined("SpecialModifier"))
                {
                    roll.setModifier((Integer)es.getProperty("SpecialModifier"));
                }
                if (spell.hasProperty("SpecialDifficulty") && es.isDefined("SpecialDifficulty"))
                {
                    roll.setDifficulty((Integer)es.getProperty("SpecialDifficulty"));
                }
                EventState evstate = new EventState(roll, EventStateType.ROLL_BEFORE, es.getPlayer());
                evstate.setPrevState(es);
                eventStack.push(evstate);
                break;
            }
            case USABLE_SPELL:
                if (es.isDefined("CastResult"))
                {
                    boolean result = (Boolean)es.getProperty("CastResult");
                    if (result)
                    {
                        EventState evstate = new EventState(es.getUsableEffect().getEvent(),
                                EventStateType.NEW_EVENT, es.getPlayer());
                        evstate.inheritProperty(es, "RollSuccesses");
                        eventStack.push(evstate);
                    }
                    else
                    {
                        addMessageCommand(cmsg, gameData.getInvestigator(es.getPlayer()).getName() +
                                " has failed to cast " + es.getUsableEffect().getSource().getName(),
                                es.getPlayer());

                    }
                    break;
                }
                else
                {
                    AHLogger.warning("resolveUsableSpell", "Castresult not defined on " +
                            es.getUsableEffect().getSource().getName());
                }
                break;
            case ROLL_BEFORE:
            {
                boolean res = resolveRollBefore(es, cmsg);
                if (res)
                {
                    return;
                }
            }
                break; // end of ROLL_BEFORE
            case ROLL_DURING:
                /* During rolls:
                 * 1. The first time entering this phase, just return the result
                 * of the die roll.
                 * 2. If the user accepts the roll by clicking OK, pass the result
                 * to the ROLL_AFTER phase
                 * 3. If the user adds clues, add more dice and return the result
                 * 4. If the user adds mod effects,
                 *    a. For non spells, execute the effect and return the result
                 *    b. For spells, set the "RollEffect" property, then
                 *    branch into SPELL_ACTIVATION.
                 */
                RollType rolltype = es.getRoll();
                String skillStr = rolltype.getType();
                InvestigatorSkill priSkill = InvestigatorSkill.getPrimarySkill(skillStr);
                int rollDiff = (rolltype.getDifficulty() == null) ? 1 : rolltype.getDifficulty();
                String rollstr = skillStr + "," + rolltype.getModifier() + "," +
                    rollDiff;
                for (int die : es.getDice())
                {
                    rollstr += "," + die;
                }
                AHLogger.info("RollDuring", "Roll is " + es.getDice().toString());
                Investigator inv = gameData.getInvestigator(es.getPlayer());
                if (!es.isDefined("RollFlag"))
                {
                    cmsg.addCommand("*Roll", rollstr, es.getPlayer());
                    es.addProperty("RollFlag", true);
                    eventStack.push(es);
                    return;
                }
                if (command == null)
                {
                    // TODO: This will be used when spells return
                    AHLogger.warning("RollDuring", "null command");
                }
                else if (command.getB().equals("Ok"))
                {
                    /*
                     * The player has accepted the roll. For sanity and stamina rolls,
                     * return to previous state as no further modifiers are possible.
                     * For skill checks, move to ROLL_AFTER
                     */
                    if (skillStr.equals("Sanity") || skillStr.equals("Stamina"))
                    {
                        EventState evstate = es.getPrevState();
                        evstate.addProperty("RollResult", "Dice");
                        evstate.getDice().clear();
                        evstate.getDice().addAll(es.getDice());
                        eventStack.push(evstate);
                        break;
                    }
                    else
                    {
                        es.setType(EventStateType.ROLL_AFTER);
                        eventStack.push(es);
                        break;
                    }
                }
                else if (command.getB().equals("Clue"))
                {
                    if (inv.getClues() > 0)
                    {
                        /* Check for the "Extra Clue Die" modeffect which
                         * gives additional dice from adding clues */
                        List<ModEffect> modlist = inv.searchModifiers("Extra Clue Die");
                        int numMods = 0;
                        for (ModEffect mod : modlist)
                        {
                            if (mod.getUsage().equals(EffectUsage.ALWAYS))
                            {
                                if (priSkill.toString().equals(mod.getDetails()))
                                {
                                    numMods++;
                                }
                                else if (InvestigatorSkill.isSecondarySkill(skillStr)
                                        && skillStr.equals(mod.getDetails()))
                                {
                                    numMods++;
                                }
                                else if (mod.getDetails().equals("All"))
                                {
                                    numMods++;
                                }
                            }
                        }
                        for (int i = 0; i < numMods + 1; i++)
                        {
                            // add one die plus bonuses
                            int die = gameData.rollOneDie();
                            es.getDice().add(die);
                        }
                        inv.modifyClues(-1);
                        cmsg.addCommand("#AddClue", "-1", es.getPlayer());
                    }
                    else
                    {
                        AHLogger.error("RollDuring", inv.getName() + " trying to use a " +
                        		"non-existent clue");
                        continue;
                    }
                } // end of using clues during rolls
                else
                {
                    // deal with mod effects
                    String[] tokens = command.getB().split(",");
                    ModEffect mod = inv.searchModifier(tokens[0], tokens[1], tokens[2]);
                    if (mod == null)
                    {
                        AHLogger.error("RollDuring", "Non-existent mod selected.");
                        cmsg.addCommand("*Roll", rollstr, es.getPlayer());
                        eventStack.push(es);
                        return;
                    }
                    if (mod.requiresCast())
                    {
                        // TODO: deal with spells
                    }
                    else
                    {
                        /* Check for usage, and consume the mod effect */
                        if (!mod.getUsage().equals(EffectUsage.LIMITED_MULTI))
                        {
                            cmsg.addCommand("#Consume", mod.getIdentifier(), es.getPlayer());
                            gameData.consumeModEffect(inv, mod);
                            es.addProperty("RollEffect", mod);
                            if (tokens.length > 2)
                            {
                                es.addProperty("RollEffectParams",
                                        Arrays.copyOfRange(tokens, 3, tokens.length));
                            }
                        }
                        else
                        {
                            /* TODO: Reimplement when there are some.
                             * There are none right now for during rolls */
                            int numUses = XmlTools.parseInt(tokens[3]);
                            cmsg.addCommand("#Consume", mod.getIdentifier() + "," +
                                    tokens[3], es.getPlayer());
                            gameData.consumeEffect(inv, mod, numUses);
                            es.addProperty("RollEffect", mod);
                            if (tokens.length > 3)
                            {
                                es.addProperty("RollEffectParams",
                                        Arrays.copyOfRange(tokens, 4, tokens.length));
                            }
                        }
                        // check for insanity/unconsciousness
                        if (checkForDeath(es.getPlayer()))
                        {
                            break;
                        }
                    }
                }
                /* Execute the selected mod effect, if one is chosen */
                if (es.isDefined("RollEffect"))
                {
                    ModEffect mod = (ModEffect)es.getProperty("RollEffect");
                    String[] tokens = (String[])es.getProperty("RollEffectParams");
                    if (mod.getDetails().equals("Reroll All"))
                    {
                        int numDice = es.getDice().size();
                        es.getDice().clear();
                        for (int i = 0; i < numDice; i++)
                        {
                            int die = gameData.rollOneDie();
                            es.getDice().add(die);
                        }
                        addMessageCommand(cmsg, inv.getName() + " used " + mod.getSource().getName() +
                                " to reroll all dice.", es.getPlayer());
                    }
                    else if (mod.getDetails().equals("Reroll Dice") && tokens != null)
                    {
                        int numDice = Math.min(mod.getAmountAsInt(), tokens.length);
                        for (int i = 0; i < numDice; i++)
                        {
                            int index = XmlTools.parseInt(tokens[i]);
                            int die = gameData.rollOneDie();
                            es.getDice().set(index, die);
                        }
                        addMessageCommand(cmsg, inv.getName() + " used " + mod.getSource().getName() +
                                " to reroll " + numDice + " dice.", es.getPlayer());
                    }
                    es.clearProperty("RollEffect");
                    es.clearProperty("RollEffectParams");
                }

                /* Finalize roll */
                rollstr = skillStr + "," + rolltype.getModifier() + "," + rollDiff;
                for (int die : es.getDice())
                {
                    rollstr += "," + die;
                }
                cmsg.addCommand("*Roll", rollstr, es.getPlayer());
                eventStack.push(es);
                return;
            case ROLL_AFTER:
                /* No ROLL_AFTER effects in base set, just return */
            {
                EventState evstate = es.getPrevState();
                evstate.addProperty("RollResult", "Dice");
                evstate.getDice().clear();
                evstate.getDice().addAll(es.getDice());
                eventStack.push(evstate);
                break;
            }
            case EVENT_CHOICE:
            {
                /*
                 * If not chosen:
                 * 1. Check if player can pay the cost
                 * 2. If he can, prompt user to make a choice
                 * 3. If not, go straight to the fail option
                 *
                 * Types of cost payable:
                 * Money, Clue, Item, Spell, Monster Trophy, Gate Trophy
                 * Sanity, Stamina
                 * Specific card:whiskey, food
                 */
                EventNode en = es.getNode();
                ChoiceType choice = en.getChoice();
                String cost = choice.getCost();
                inv = gameData.getInvestigator(es.getPlayer());
                if (command == null)
                {
                    // check if cost is payable
                    boolean payable = gameData.canPayCost(inv, cost, choice.getAmount());
                    if (payable)
                    {
                        // prompt for a choice
                        cmsg.addCommand("!PlayerChoice", choice.getDescription(), es.getPlayer());
                        eventStack.push(es);
                        return;
                    }
                    else
                    {
                        if (en.getChoicefail() != null)
                        {
                            EventState evstate = new EventState(en.getChoicefail(),
                                    EventStateType.NEW_EVENT, es.getPlayer());
                            eventStack.push(evstate);
                        }
                        break;
                    }
                }
                if (!command.getA().equals("!PlayerChoice"))
                {
                    AHLogger.error("resolveEventChoice", "Invalid command received: " + command);
                    break;
                }
                if (command.getB().equals("Yes"))
                {
                    if (en.getChoicepass() != null)
                    {
                        EventState evstate = new EventState(en.getChoicepass(),
                                EventStateType.NEW_EVENT, es.getPlayer());
                        eventStack.push(evstate);
                    }
                    // pay the cost
                    if (cost.contains("Sanity") || cost.contains("Stamina"))
                    {
                        EventNode e1 = EventUtils.createEffectNode("Damage", cost, choice.getAmount());
                        EventState evstate = new EventState(e1, EventStateType.NEW_EVENT,
                                es.getPlayer());
                        eventStack.push(evstate);
                    }
                    else if (!cost.equals("None"))
                    {
                        EventNode e1 = EventUtils.createEffectNode("Investigator Loses",
                                cost, choice.getAmount());
                        EventState evstate = new EventState(e1, EventStateType.NEW_EVENT,
                                es.getPlayer());
                        eventStack.push(evstate);
                    }
                    break;
                }
                else
                {
                    if (en.getChoicefail() != null)
                    {
                        EventState evstate = new EventState(en.getChoicefail(),
                                EventStateType.NEW_EVENT, es.getPlayer());
                        eventStack.push(evstate);
                    }
                    break;
                }
            }
            case MOVE_MONSTER:
                if (!es.isDefined("CombatResult"))
                {
                    AHLogger.error("resolveMoveMonster", "CombatResult is not defined.");
                    break;
                }
                if (es.getProperty("CombatResult").equals("Evaded"))
                {
                    /* Evaded the enemy */
                    Monster mon = es.getMonster();
                    gameData.removeFromCombatFrame(mon);
                    break;
                }
                else if (es.getProperty("CombatResult").equals("Victory"))
                {
                    Monster mon = es.getMonster();
                    gameData.removeFromCombatFrame(mon);
                    inv = gameData.getInvestigator(es.getPlayer());
                    inv.setMovementPointsLocked(true);
                }
                break;
            case ENCOUNTER_MONSTER:
            {
                Monster mon = es.getMonster();
                if (!es.isDefined("CombatResult"))
                {
                    // Prompt user whether he wishes to fight or evade
                    if (command == null)
                    {
                        cmsg.addCommand("!MonsterAppears", String.valueOf(mon.getId()), es.getPlayer());
                        eventStack.push(es);
                        return;
                    }
                    if (command.getB().equals("Fight"))
                    {

                    }
                }
                if (es.getProperty("CombatResult").equals("Evaded"))
                {
                    /* Evaded the enemy */

                    break;
                }
                else if (es.getProperty("CombatResult").equals("Victory"))
                {

                }
                else
                {

                }
                break;
            }
            case MONSTER_EVADE:
                if (es.isDefined("RollResult"))
                {
                    Monster mon = es.getMonster();
                    inv = gameData.getInvestigator(es.getPlayer());
                    String rollResult = (String)es.getProperty("RollResult");
                    int successes = 0, difficulty = 1;
                    if (rollResult.equals("Dice"))
                    {
                        inv = gameData.getInvestigator(es.getPlayer());
                        successes = countSuccesses(inv, "Evade", 0, es.getDice(), true);
                    }
                    else if (rollResult.equals("Autopass"))
                    {
                        successes = difficulty;
                    }
                    if (successes >= difficulty)
                    {
                        EventState prevState = es.getPrevState();
                        prevState.addProperty("CombatResult", "Evaded");
                        eventStack.push(prevState);
                        addMessageCommand(cmsg, inv.getName() + " has evaded " + mon.getName(),
                                es.getPlayer());
                        break;
                    }
                    else
                    {
                        /* Enqueue combat damage, then move to horror check
                         * Enable 'evadeOption' to give the player the option to
                         * evade after the Horror check.
                         * If the horror check is already done (ie, this evade
                         * attempt is done after the first round of combat),
                         * then advance to Combat check
                         */
                        es.clearProperty("RollResult");
                        es.addProperty("EvadeOption", Boolean.TRUE);
                        if (es.isDefined("HorrorCheckDone"))
                        {
                            es.setType(EventStateType.MONSTER_COMBAT);
                            es.clearProperty("HandsLeft");
                        }
                        else
                        {
                            es.setType(EventStateType.MONSTER_HORROR);
                        }
                        eventStack.push(es);
                        EventNode en = monsterCombatDamage(cmsg, mon);
                        EventState evstate = new EventState(en, EventStateType.NEW_EVENT,
                                es.getPlayer());
                        evstate.setMessage(inv.getName() + " has failed to evade " + mon.getName());
                        eventStack.push(evstate);
                    }
                    break;
                }
                else
                {
                    // check for mod-effects specifically for evading monsters
                    Monster mon = es.getMonster();
                    int awareness = gameData.getModifiedStat(mon, Monster.STAT_AWARENESS);
                    inv = gameData.getInvestigator(es.getPlayer());
                    List<ModEffect> modlist = inv.searchModifiers("Evade Monster");
                    GameData.removeUnavailableEffects(inv, modlist);
                    boolean modsUsed = false;
                    if (es.isModEffectsSelected())
                    {
                        if (!es.getModEffects().isEmpty())
                        {
                            ModEffect mod = es.getModEffects().get(0);
                            if (mod.getDetails().equals("Automatically Pass"))
                            {
                                // For auto-pass, set combat result to evaded
                                EventState prevstate = es.getPrevState();
                                prevstate.addProperty("CombatResult", "Evaded");
                                prevstate.setMessage(inv.getName() + " used " +
                                        mod.getSource().getName() +
                                        " to automatically pass the Evade check.");
                                eventStack.push(prevstate);
                                break;
                            }
                        }
                        else
                        {
                            modsUsed = true;
                        }
                    }
                    if (modlist.size() == 0 || modsUsed)
                    {
                        // no effects used, move to roll
                        RollType roll = EventUtils.createRollType("Evade", awareness, 1);
                        EventState evstate = new EventState(roll, EventStateType.ROLL_BEFORE,
                                es.getPlayer());
                        evstate.setPrevState(es);
                        eventStack.push(evstate);
                        break;
                    }
                    else
                    {
                        // request for mod effects
                        EventState evstate = new EventState(modlist, EventStateType.MODIFIER_USAGE,
                                es.getPlayer());
                        // IMPORTANT: EvadeMonster mod effect must specify specialmodifier
                        evstate.addProperty("SpecialModifier", new Integer(awareness));
                        evstate.setPrevState(es);
                        cmsg.addCommand("!EvadeMonster", String.valueOf(mon.getId()), es.getPlayer());
                        eventStack.push(evstate);
                        return;
                    }
                }
            case MONSTER_HORROR:
            {
                Monster mon = es.getMonster();
                int horrorRating = gameData.getModifiedStat(mon, Monster.STAT_HORROR_RATING);
                if (es.isDefined("RollResult"))
                {
                    inv = gameData.getInvestigator(es.getPlayer());
                    String rollResult = (String)es.getProperty("RollResult");
                    int successes = 0, difficulty = 1;
                    if (rollResult.equals("Dice"))
                    {
                        inv = gameData.getInvestigator(es.getPlayer());
                        successes = countSuccesses(inv, "Horror", 0, es.getDice(), true);
                    }
                    else if (rollResult.equals("Autopass"))
                    {
                        successes = difficulty;
                    }
                    if (successes >= difficulty)
                    {
                        /* Passed. Check for nightmarish, otherwise move to combat */
                        int nightmarish = mon.getNightmarishValue();
                        List<ModEffect> list = inv.searchModifiers("Negate", "Nightmarish");
                        for (ModEffect mod : list)
                        {
                            //TODO: For expansion, deal with mod-effects that are not always on
                            if (mod.getUsage().equals(EffectUsage.ALWAYS))
                            {
                                nightmarish = 0;
                                break;
                            }
                        }
                        /* Move to COMBAT check */
                        es.clearProperty("RollResult");
                        es.addProperty("HorrorCheckDone", true);
                        es.setType(EventStateType.MONSTER_COMBAT);
                        eventStack.push(es);
                        if (nightmarish > 0)
                        {
                            EventNode en = EventUtils.createEffectNode("Damage", "Sanity",
                                    String.valueOf(nightmarish));
                            EventState evstate = new EventState(en, EventStateType.NEW_EVENT,
                                    es.getPlayer());
                            eventStack.push(evstate);
                        }
                        break;
                    }
                    else
                    {
                        /* Failed, take horror damage */
                        es.clearProperty("RollResult");
                        es.addProperty("HorrorCheckDone", true);
                        es.setType(EventStateType.MONSTER_COMBAT);
                        eventStack.push(es);
                        EventNode en = monsterHorrorDamage(cmsg, mon);
                        EventState evstate = new EventState(en, EventStateType.NEW_EVENT,
                                es.getPlayer());
                        eventStack.push(evstate);
                        break;
                    }
                }
                else // if (!es.isDefined("RollResult"))
                {
                    MonsterPower mp = mon.getSpecificPower(MonsterPowerActivation.BEFORE_HORROR);
                    if (mp != null)
                    {
                        //TODO: deal with Black man and Bloated Woman
                    }
                    if (horrorRating == Monster.NO_HORROR_CHECK_RATING)
                    {
                        /* No horror check, move to combat */
                        es.addProperty("HorrorCheckDone", true);
                        es.setType(EventStateType.MONSTER_COMBAT);
                        eventStack.push(es);
                        break;
                    }
                    RollType roll = EventUtils.createRollType("Horror", horrorRating, 1);
                    EventState evstate = new EventState(roll, EventStateType.ROLL_BEFORE,
                            es.getPlayer());
                    evstate.setPrevState(es);
                    eventStack.push(evstate);
                }
            }
                break;
            case MONSTER_COMBAT:
            {
                Monster mon = es.getMonster();
                int combatRating = gameData.getModifiedStat(mon, Monster.STAT_COMBAT_RATING);
                int toughness = gameData.getModifiedStat(mon, Monster.STAT_TOUGHNESS);
                int physResState = mon.getPhysicalDefences();
                int magResState = mon.getMagicalDefences();
                inv = gameData.getInvestigator(es.getPlayer());
                /* Apply effects of red sign */
                if (es.isDefined("RedSign"))
                {
                    List<?> redSignList = (List<?>)es.getProperty("RedSign");
                    if (redSignList.contains("Physical Resistance") ||
                            redSignList.contains("Physical Immunity"))
                    {
                        physResState = Globals.NONE;
                    }
                    if (redSignList.contains("Magical Resistance")
                            && magResState == Globals.RESISTANCE)
                    {
                        magResState = Globals.NONE;
                    }
                    toughness = Math.min(1, toughness - redSignList.size());
                }

                if (es.isDefined("RollResult"))
                {
                    String rollResult = (String)es.getProperty("RollResult");
                    int successes = 0, difficulty = toughness;
                    if (rollResult.equals("Dice"))
                    {
                        inv = gameData.getInvestigator(es.getPlayer());
                        successes = countSuccesses(inv, "Combat", 0, es.getDice(), true);
                        if (es.isDefined("Count6sTwice"))
                        {
                            successes += GameData.countOccurences(es.getDice(), 6);
                        }
                    }
                    else if (rollResult.equals("Autopass"))
                    {
                        successes = difficulty;
                    }
                    if (successes >= difficulty)
                    {
                        /* Passed. Check for overwhelming, otherwise return to previous
                         * state */
                        int overwhelming = mon.getOverwhelmingValue();
                        List<ModEffect> list = inv.searchModifiers("Negate", "Overwhelming");
                        for (ModEffect mod : list)
                        {
                            //TODO: For expansion, deal with mod-effects that are not always on
                            if (mod.getUsage().equals(EffectUsage.ALWAYS))
                            {
                                overwhelming = 0;
                                break;
                            }
                        }
                        if (EventUtils.listPropertyContains(es, "RedSign", "Overwhelming"))
                        {
                            overwhelming = 0;
                        }
                        EventState prevstate = es.getPrevState();
                        prevstate.addProperty("CombatResult", "Victory");
                        boolean endless = mon.hasAttribute(MonsterAttribute.ENDLESS) &&
                                !EventUtils.listPropertyContains(es, "RedSign", "Endless");
                        EventNode en1 = EventUtils.createEffectNode("Defeat Monster",
                                String.valueOf(mon.getId()), endless ? "-1" : "1");
                        EventState winstate = new EventState(en1, EventStateType.NEW_EVENT,
                                es.getPlayer());
                        eventStack.push(prevstate);
                        if (overwhelming > 0)
                        {
                            EventNode en2 = EventUtils.createEffectNode("Damage", "Stamina",
                                    String.valueOf(overwhelming));
                            EventState evstate = new EventState(en2, EventStateType.NEW_EVENT,
                                    es.getPlayer());
                            eventStack.push(evstate);
                        }
                        eventStack.push(winstate);
                    }
                    else
                    {
                        /* Failed,
                         * 1. Remove all "Weapons" from equipped list
                         * 2. Clear RollResult
                         * 3. Define EvadeOption so the investigator can flee in
                         * future rounds
                         * 4. Queue the damage events
                         * */
                        List<EffectSource> equippedWeapons =
                            (List<EffectSource>)es.getProperty("EquippedWeapons");

                        for (int i = 0; i < equippedWeapons.size(); i++)
                        {
                            EffectSource effsrc = equippedWeapons.get(i);
                            List<ModEffect> list = effsrc.searchModEffects("Weapon");
                            if (list.size() == 1)
                            {
                                equippedWeapons.remove(i);
                                i--;
                            }
                        }
                        /* Recalculate Hands */
                        es.addProperty("HandsLeft", 2);
                        for (EffectSource effsrc : equippedWeapons)
                        {
                            es.modifyIntegerProperty("HandsLeft", -GameData.getNumHands(effsrc));
                        }
                        es.clearProperty("RollResult");
                        es.addProperty("EvadeOption", true);
                        eventStack.push(es);
                        EventNode en = monsterCombatDamage(cmsg, mon);
                        EventState evstate = new EventState(en, EventStateType.NEW_EVENT,
                                es.getPlayer());
                        eventStack.push(evstate);
                        break;
                    }
                }
                else
                {
                    /* 1. Check for "EvadeOption"
                     * 2. Request for weapons usage or the Evade Option
                     * 3. If Evade option is used, move to monster_evade
                     * 4. Otherwise, add triggered weapons to a list until all
                     *    weapons have been selected.
                     * 5. Make the roll
                     */
                    boolean canEvade = es.isDefined("EvadeOption") &&
                        !mon.hasAttribute(MonsterAttribute.AMBUSH);
                    List<ModEffect> negateList = inv.searchModifiers("Negate");

                    for (ModEffect mod : negateList)
                    {
                        //TODO: for expansion, deal with non-always cases
                        if (mod.getUsage().equals(EffectUsage.ALWAYS))
                        {
                            if (mod.getDetails().equals("Physical Resistance") &&
                                    physResState == Globals.RESISTANCE)
                            {
                                physResState = Globals.NONE;
                            }
                            if (mod.getDetails().equals("Magical Resistance") &&
                                    magResState == Globals.RESISTANCE)
                            {
                                magResState = Globals.NONE;
                            }
                        }
                    }
                    String combatStr = mon.getId() + "," + combatRating + "," + toughness + "," +
                        physResState + "," + magResState + ",2," + canEvade;
                    String equippedStr = "";
                    if (!es.isDefined("HandsLeft"))
                    {
                        /* This part is executed the first time the combat sequence is entered */
                        es.addProperty("HandsLeft", new Integer(2));
                        es.addProperty("EquippedWeapons", new ArrayList<EffectSource>());
                        es.getModEffects().clear();
                        cmsg.addCommand("*Combat", combatStr);
                        eventStack.push(es);
                        return;
                    }
                    int handsLeft = (Integer)es.getProperty("HandsLeft");
                    List<EffectSource> equippedWeapons =
                        (List<EffectSource>)es.getProperty("EquippedWeapons");
                    for (EffectSource effsrc : equippedWeapons)
                    {
                        equippedStr += "," + effsrc.getIdentifier();
                    }
                    if (command == null)
                    {
                        /* Command will be null in two cases: when spells return,
                         * and when re-entering the MONSTER_COMBAT state from a failed
                         * combat check.
                         */
                        if (es.isDefined("CombatEffect"))
                        {
                            // returning spell
                            if (!es.isDefined("CastResult"))
                            {
                                AHLogger.error("resolveMonsterCombat", "Spell returned with no " +
                                		"result");
                                break;
                            }
                            boolean result = (Boolean)es.getProperty("CastResult");
                            if (!result)
                            {
                                // Spell failed, clear combat effect
                                es.clearProperty("CombatEffect");
                                es.clearProperty("CombatEffectParams");
                            }
                        }
                    }
                    else if (command.getB().equals("Evade"))
                    {
                        // The investigator has chosen to evade, move to evade check
                        es.clearProperty("HandsLeft");
                        es.setType(EventStateType.MONSTER_EVADE);
                        eventStack.push(es);
                        break;
                    }
                    else if (command.getB().startsWith("Ok"))
                    {
                        // Finalize effects and calculate combat modifiers
                        String[] tokens = command.getB().split(",");
                        for (int i = 1; i < tokens.length; i++)
                        {
                            EffectSource effsrc = inv.searchEffectSource(tokens[i]);
                            /* NOTE: Not doing a server side check here for hands.
                             * I'll assume the client side check is enough.
                             */
                            equippedWeapons.add(effsrc);
                            es.modifyIntegerProperty("HandsLeft", -GameData.getNumHands(effsrc));
                        }
                        int bonusDice = 0;
                        boolean count6sTwice = false;
                        for (EffectSource effsrc : equippedWeapons)
                        {
                            List<ModEffect> tlist = effsrc.searchModEffects("Weapon");
                            if (tlist.size() == 1)
                            {
                                ModEffect mod = tlist.get(0);
                                int val = mod.getAmountAsInt();
                                // apply weapon bonuses first, then apply resistances
                                List<ModEffect> bonuses = effsrc.searchModEffects("Weapon Bonus");
                                for (ModEffect bonus : bonuses)
                                {
                                    if (bonus.getDetails().equals("Other Hand Empty") &&
                                            equippedWeapons.size() == 1)
                                    {
                                        val += bonus.getAmountAsInt();
                                    }
                                    else if (bonus.getDetails().equals("Vs Undead") &&
                                            mon.hasAttribute(MonsterAttribute.UNDEAD))
                                    {
                                        val += bonus.getAmountAsInt();
                                    }
                                    else if (bonus.getDetails().equals("6s Count Twice"))
                                    {
                                        count6sTwice = true;
                                    }
                                }
                                if (mod.getDetails().equals("Magical") ||
                                        EventUtils.listPropertyContains(es, "EnchantWeapon",
                                        mod.getSource().getName()))
                                {
                                    if (magResState == Globals.RESISTANCE)
                                    {
                                        val = (val + 1) / 2;
                                    }
                                    else if (magResState == Globals.IMMUNITY)
                                    {
                                        val = 0;
                                    }
                                }
                                else
                                {
                                    if (physResState == Globals.RESISTANCE)
                                    {
                                        val = (val + 1) / 2;
                                    }
                                    else if (physResState == Globals.IMMUNITY)
                                    {
                                        val = 0;
                                    }
                                }
                                bonusDice += val;
                                continue;
                            }
                            List<ModEffect> slist = effsrc.searchModEffects("Combat Spell",
                                    "Combat Bonus");
                            if (slist.size() == 1)
                            {
                                int val = slist.get(0).getAmountAsInt();
                                if (magResState == Globals.RESISTANCE)
                                {
                                    val = (val + 1) / 2;
                                }
                                bonusDice += val;
                            }
                        }
                        RollType roll = EventUtils.createRollType("Combat", combatRating, toughness);
                        EventState evstate = new EventState(roll, EventStateType.ROLL_BEFORE,
                                es.getPlayer());
                        evstate.addProperty("BonusDice", bonusDice);
                        if (count6sTwice)
                        {
                            es.addProperty("Count6sTwice", true);
                        }
                        else
                        {
                            es.clearProperty("Count6sTwice");
                        }
                        evstate.setPrevState(es);
                        eventStack.push(evstate);
                        break;
                    } // end of "Ok"
                    else
                    {
                        // Investigator is using effects
                        String[] tokens = command.getB().split(",");
                        ModEffect mod = inv.searchModifier(tokens[0], tokens[1], tokens[2]);
                        if (mod == null)
                        {
                            AHLogger.error("RollDuring", "Non-existent mod selected.");
                            cmsg.addCommand("*Combat", combatStr + equippedStr, es.getPlayer());
                            eventStack.push(es);
                            return;
                        }
                        if (mod.requiresCast())
                        {
                            if (!(mod.getSource() instanceof Spell))
                            {
                                AHLogger.error("resolveModUsage", "Cast type specified " +
                                        " on a non-spell mod-effect");
                                break;
                            }
                            Spell spell = (Spell)mod.getSource();
                            // use up the number of hands
                            if (spell.getHands() > handsLeft)
                            {
                                AHLogger.error("Not enough hands for " + spell.getName());
                                addMessageCommand(cmsg, "Not enough hands to use " + spell.getName(),
                                        es.getPlayer());
                                cmsg.addCommand("*Combat", combatStr + equippedStr, es.getPlayer());
                                eventStack.push(es);
                                return;
                            }
                            es.modifyIntegerProperty("HandsLeft", -spell.getHands());
                            es.addProperty("CombatEffect", mod);
                            if (tokens.length > 2)
                            {
                                es.addProperty("CombatEffectParams",
                                        Arrays.copyOfRange(tokens, 3, tokens.length));
                            }
                            EventState evstate = new EventState(spell,
                                    EventStateType.SPELL_ACTIVATION, es.getPlayer());
                            evstate.setPrevState(es);
                            // Set special difficulty for Bind Monster
                            if (mod.getType().equals("Combat Spell") &&
                                    mod.getDetails().equals("Automatically Pass"))
                            {
                                evstate.addProperty("SpecialDifficulty", toughness);
                            }
                            cmsg.addCommand("#Consume", mod.getIdentifier(), es.getPlayer());
                            gameData.consumeModEffect(inv, mod);
                            eventStack.push(evstate);
                            checkForDeath(es.getPlayer());
                            break;
                        }
                        else // (if mod.requiresCast())
                        {
                            /* Check for usage, and consume the mod effect */
                            if (!mod.getUsage().equals(EffectUsage.LIMITED_MULTI))
                            {
                                cmsg.addCommand("#Consume", mod.getIdentifier(), es.getPlayer());
                                gameData.consumeModEffect(inv, mod);
                                es.addProperty("CombatEffect", mod);
                                if (tokens.length > 2)
                                {
                                    es.addProperty("CombatEffectParams",
                                            Arrays.copyOfRange(tokens, 3, tokens.length));
                                }
                            }
                            else
                            {
                                /* TODO: reimplement when there are such effects. There
                                 * are none right now for combat
                                 */
                                int numUses = XmlTools.parseInt(tokens[3]);
                                cmsg.addCommand("#Consume", mod.getIdentifier() + "," +
                                        tokens[3], es.getPlayer());
                                gameData.consumeEffect(inv, mod, numUses);
                                es.addProperty("CombatEffect", mod);
                                if (tokens.length > 3)
                                {
                                    es.addProperty("CombatEffectParams",
                                            Arrays.copyOfRange(tokens, 4, tokens.length));
                                }
                            }
                            // check for insanity/unconsciousness
                            if (checkForDeath(es.getPlayer()))
                            {
                                break;
                            }
                            // check for hands
                            int hands = GameData.getNumHands(mod.getSource());
                            if (handsLeft < hands)
                            {
                                AHLogger.error("Not enough hands for " + mod.getSource().getName());
                                addMessageCommand(cmsg, "Not enough hands to use " +
                                        mod.getSource().getName(), es.getPlayer());
                                cmsg.addCommand("*Combat", combatStr + equippedStr, es.getPlayer());
                                eventStack.push(es);
                                return;
                            }
                            es.modifyIntegerProperty("HandsLeft", -hands);
                        }
                    } // end of "use effect"

                    if (es.isDefined("CombatEffect"))
                    {
                        // Process the combat effect
                        ModEffect mod = (ModEffect)es.getProperty("CombatEffect");
                        if (mod.getType().equals("Weapon"))
                        {
                            /* For weapons, just add it to the list of equipped items */
                            equippedWeapons.add(mod.getSource());
                        }
                        else if (mod.getType().equals("Combat Spell") &&
                                magResState != Globals.IMMUNITY)
                        {
                            if (mod.getDetails().equals("Combat Bonus"))
                            {
                                equippedWeapons.add(mod.getSource());
                            }
                            else if (mod.getDetails().equals("Automatically Pass"))
                            {
                                // auto-pass combat check, wooh!
                                // define rollResult, and redo the state
                                es.addProperty("RollResult", "Autopass");
                                eventStack.push(es);
                                break;
                            }
                            else if (mod.getDetails().equals("Enchant Weapon"))
                            {
                                List<String> list = new ArrayList<String>();
                                String[] params = (String[])es.getProperty("CombatEffectParams");
                                list.add(params[0]);
                                addMessageCommand(cmsg, params[0] + " has been enchanted.",
                                        es.getPlayer());
                                es.addProperty("EnchantWeapon", list);
                            }
                            else if (mod.getDetails().equals("Red Sign"))
                            {
                                List<String> list = new ArrayList<String>();
                                String[] params = (String[])es.getProperty("CombatEffectParams");
                                list.add(params[0]);
                                es.addProperty("RedSign", list);
                                equippedWeapons.add(mod.getSource());
                                toughness = Math.min(1, toughness - 1);
                                if (params[0].equals("Physical Resistance") ||
                                        params[0].equals("Physical Immunity"))
                                {
                                    physResState = Globals.NONE;
                                }
                                else if (params[0].equals("Magical Resistance"))
                                {
                                   magResState = Globals.NONE;
                                }
                                addMessageCommand(cmsg, "Red Sign of Shudde M'ell successfully cast.",
                                        es.getPlayer());
                            }
                        }
                        es.clearProperty("CombatEffect");
                        es.clearProperty("CombatEffectParams");
                    }

                    combatStr = mon.getId() + "," + combatRating + "," + toughness + "," +
                        physResState + "," + magResState + "," + es.getProperty("HandsLeft") +
                        "," + canEvade;
                    /* Return control to the player */
                    for (EffectSource effsrc : equippedWeapons)
                    {
                        combatStr += "," + effsrc.getIdentifier();
                    }
                    cmsg.addCommand("*Combat", combatStr, es.getPlayer());
                    eventStack.push(es);
                    return;
                }
            }
                break;
            case EFFECT_DECISION:
                EffectType effect = es.getEffect();
                if (command == null)
                {
                    AHLogger.info("EffectDecision", "Resolving " + EventUtils.printEffect(effect) +
                            " without input.");
                    boolean res = resolveEffect(es, cmsg);
                    if (res)
                    {
                        return;
                    }
                    continue;
                }
                else if (command.getA().equals("!Invcardchoice") ||
                        command.getA().equals("!Losecardchoice") ||
                        command.getA().equals("!SurgePlacement") ||
                        command.getA().equals("!AllocateClue") ||
                        command.getA().equals("!HealTarget") ||
                        command.getA().equals("!Encounter") ||
                        command.getA().equals("!OWEncounter"))
                {
                    /* Set of commands that can be handled by setting the
                     * appropriate property
                     */
                    es.addProperty(command.getA(), command.getB());
                    boolean res = resolveEffect(es, cmsg);
                    if (res)
                    {
                        return;
                    }
                } // end of !Invcardchoice and others
                else if (command.getA().equals("!Scrounge"))
                {
                    String prop = (String)es.getProperty("!Scrounge");
                    String[] tokens = prop.split(",");
                    Deck <? extends InvestigatorCard> deck = gameData.getDeck(tokens[0]);
                    int left = XmlTools.parseInt(tokens[1]);
                    InvestigatorCard card = null;
                    if (command.getB().equals("Scrounge-Top"))
                    {
                        card = deck.tempDeal();
                    }
                    else if (command.getB().equals("Scrounge-Bottom"))
                    {
                        card = deck.tempDealBottom();
                    }
                    else
                    {
                        eventStack.push(es);
                        return;
                    }
                    String details = tokens[0] + "," + String.valueOf(left - 1);
                    for (int i = 2; i < tokens.length; i++)
                    {
                        details += "," + tokens[i];
                    }
                    details += "," + card.getId();
                    es.addProperty("!Scrounge", details);
                    if (left == 1)
                    {
                        boolean res = resolveEffect(es, cmsg);
                        if (res)
                        {
                            eventStack.push(es);
                            return;
                        }
                    }
                    else
                    {
                        cmsg.addCommand("!Scrounge", details);
                        eventStack.push(es);
                        return;
                    }
                } // end of !Scrounge
                else
                {
                    AHLogger.warning("resolveEffectDecision", "Unknown command: " + command.getA());
                }
                break; // end of EFFECT_DECISION
            case MODIFIER_USAGE:
                List<ModEffect> modList = es.getModEffects();
                AHLogger.info("GameScreen", "Processing mod effects: " + command + " " + modList);
                EventState prevState = es.getPrevState();
                inv = gameData.getInvestigator(es.getPlayer());
                prevState.setModEffectsSelected(true);
                /*
                 * In all cases: mod-effects resolve in the following order
                 * 1. Check received command for the use of any mod effects, consume
                 *    the mod effect if used.
                 * 2. Pop the previous state which is mostly effect decision
                 * 3. Push previous state on the top of the stack with any selected
                 * mod effects
                 */
                if (command == null)
                {
                    // Used when spells return
                    if (!es.isDefined("CastResult"))
                    {
                        AHLogger.error("resolveModUsage", "No cast result returned!");
                        break;
                    }
                    boolean result = (Boolean)es.getProperty("CastResult");
                    if (result)
                    {
                        prevState.getModEffects().add(es.getModEffects().get(0));
                        eventStack.push(prevState);
                        break;
                    }
                }
                else if (command.getA().equals("!PreventDamage") ||
                    command.getA().equals("!BeforeCheck") ||
                    command.getA().equals("!EvadeMonster") ||
                    command.getA().equals("!RedrawEncounter") ||
                    command.getA().equals("!RedrawOWEncounter"))
                {
                    /* Extract the id of the effect used, if any */
                    String[] tokens = command.getB().split(",");
                    if (tokens[0].equals("None"))
                    {
                        prevState.getModEffects().clear();
                    }
                    else
                    {
                        ModEffect mod = null;
                        for (int i = 0; i < modList.size(); i++)
                        {
                            ModEffect tempMod = modList.get(i);
                            if (tempMod.getSourceString().equals(tokens[0]) &&
                                    tempMod.getType().equals(tokens[1]) &&
                                    tempMod.getDetails().equals(tokens[2]))
                            {
                                mod = tempMod;
                                break;
                            }
                        }
                        if (mod != null)
                        {
                            if (mod.requiresCast())
                            {
                                /* Call SPELL_ACTIVATION to resolve the spell */
                                if (!(mod.getSource() instanceof Spell))
                                {
                                    AHLogger.error("resolveModUsage", "Cast type specified " +
                                            " on a non-spell mod-effect");
                                    break;
                                }
                                Spell spell = (Spell)mod.getSource();
                                EventState evstate = new EventState(spell,
                                        EventStateType.SPELL_ACTIVATION, es.getPlayer());
                                evstate.inheritProperties(es);
                                evstate.setPrevState(es);
                                es.getModEffects().clear();
                                es.getModEffects().add(mod);
                                eventStack.push(evstate);
                                cmsg.addCommand("#Consume", mod.getIdentifier(), es.getPlayer());
                                gameData.consumeModEffect(inv, mod);
                                command = null;
                                checkForDeath(es.getPlayer());
                                break;
                            }
                            /* Check for usage, and consume the mod effect */
                            if (tokens.length == 3)
                            {
                                cmsg.addCommand("#Consume", mod.getIdentifier(), es.getPlayer());
                                gameData.consumeModEffect(inv, mod);
                            }
                            else
                            {
                                int numUses = XmlTools.parseInt(tokens[3]);
                                cmsg.addCommand("#Consume", mod.getIdentifier() + "," +
                                        tokens[3], es.getPlayer());
                                prevState.addProperty(mod.getIdentifier(),
                                        new Integer(numUses));
                                gameData.consumeEffect(inv, mod, numUses);
                            }
                            prevState.getModEffects().add(mod);
                        }
                    }
                } // end of !PreventDamage
                else
                {
                    AHLogger.warning("resolveModEffectUsage", "Unknown command: " + command.getA());
                } // end of long if-else in resolve Mod effects
                command = null;
                eventStack.push(prevState);
                checkForDeath(es.getPlayer());
                break; // end of MODIFIER_USAGE
            default:

                break;
            }
            command = null;
        } // end of overall while loop

    }

    private boolean resolveRollBefore(EventState es, CommandMessage cmsg)
    {
        RollType rolltype = es.getRoll();
        Investigator inv = gameData.getInvestigator(es.getPlayer());
        AHLogger.info("RollBefore", inv.getName() + " making " + rolltype.getType() + " check");
        if (rolltype.getType().equals("Single") || rolltype.getType().equals("Special"))
        {
            /* For Single and Special rolls, no modifiers are possible, and the
             * die rolls are easily represented, so just roll the die,
             * skip ROLL_DURING, and return back to the previous state.
             */
            EventState evstate = es.getPrevState();
            int die = gameData.rollOneDie();
            evstate.addProperty("RollResult", "Dice");
            evstate.getDice().clear();
            evstate.getDice().add(die);
            evstate.setMessage("The die roll is " + die + ".");
            eventStack.push(evstate);
            return false;
        }
        else if (rolltype.getType().equals("Sanity") || rolltype.getType().equals("Stamina"))
        {
            /* For Sanity and Stamina rolls, no modifiers are possible, so just roll
             * the dice and go to the next state
             */
            int numDice = rolltype.getType().equals("Sanity") ? inv.getCurrentSanity() :
                inv.getCurrentStamina();
            for (int i = 0; i < numDice; i++)
            {
                int die = gameData.rollOneDie();
                es.getDice().add(die);
            }
            es.setType(EventStateType.ROLL_DURING);
            eventStack.push(es);
            return false;
        }
        /* Check for mod-effects that auto-pass rolls or change the number of dice
         * Number of dice is the sum of:
         * 1. Base skill at slider's position
         * 2. Items that provide "Skill Bonus"
         * 3. Environments that provide "Skill Bonus"
         * 4. Used mod-effects that provide "Before X Check" bonuses
         *
         * These are slightly different when dealing with combat.
         */
        String skillStr = rolltype.getType();
        InvestigatorSkill priSkill = InvestigatorSkill.getPrimarySkill(skillStr);
        List<ModEffect> modlist = new ArrayList<ModEffect>();
        int numDice = inv.getCurrentSkill(priSkill.index());
        List<EffectType> envBonusList = gameData.searchInvestigatorGlobalEffects(inv);
        // Check for environments that add Skill Bonus
        GameData.filterEffectList(envBonusList, "Skill Bonus");
        for (EffectType eff : envBonusList)
        {
            if (eff.getDetails().equals(priSkill.toString()))
            {
                numDice += XmlTools.parseInt(eff.getAmount());
            }
        }
        if (InvestigatorSkill.isSecondarySkill(skillStr))
        {
            /* Also check the secondary skill name in the case of
             * secondary skills
             */
            modlist.addAll(inv.searchModifiers("Before " + skillStr + " Check"));
            List<ModEffect> skillBonusList = inv.searchModifiers("Skill Bonus", skillStr);
            for (ModEffect mod : skillBonusList)
            {
                if (mod.getUsage().equals(EffectUsage.ALWAYS))
                {
                    numDice += mod.getAmountAsInt();
                }
            }
            for (EffectType eff : envBonusList)
            {
                if (eff.getDetails().equals(skillStr))
                {
                    numDice += XmlTools.parseInt(eff.getAmount());
                }
            }
        }
        modlist.addAll(inv.searchModifiers("Before " + priSkill + " Check"));
        modlist.addAll(inv.searchModifiers("Before Skill Check"));
        GameData.removeUnavailableEffects(inv, modlist);

        boolean modsUsed = false;
        numDice += rolltype.getModifier();
        if (es.isModEffectsSelected())
        {
            if (!es.getModEffects().isEmpty())
            {
                ModEffect mod = es.getModEffects().get(0);
                if (mod.getDetails().equals("Automatically Pass"))
                {
                    // For auto-pass, set RollResult to auto-pass
                    EventState evstate = es.getPrevState();
                    evstate.addProperty("RollResult", "Autopass");
                    evstate.setMessage(inv.getName() + " used " + mod.getSource().getName() +
                            " to automatically pass the " + skillStr + " check.");
                    eventStack.push(evstate);
                    return false;
                }
                else if (mod.getDetails().equals("Bonus Dice"))
                {
                    es.modifyIntegerProperty("BonusDice", mod.getAmountAsInt());
                }
            }
            else
            {
                modsUsed = true;
            }
        }
        if (modlist.size() == 0 || modsUsed)
        {
            // number of dice finalized, move to actual roll
            if (es.isDefined("BonusDice"))
            {
                numDice += (Integer)es.getProperty("BonusDice");
            }
            for (int i = 0; i < numDice; i++)
            {
                int die = gameData.rollOneDie();
                es.getDice().add(die);
            }
            es.setType(EventStateType.ROLL_DURING);
            eventStack.push(es);
            return false;
        }
        else
        {
            // request for mod effects
            EventState evstate = new EventState(modlist, EventStateType.MODIFIER_USAGE,
                    es.getPlayer());
            evstate.setPrevState(es);
            cmsg.addCommand("!BeforeCheck", skillStr + "," + rolltype.getModifier() +
                    "," + numDice);
            eventStack.push(evstate);
            return true;
        }
    }

    /**
     * Resolves an effect and returns true if any follow up action needs to be done.
     * As a side effect, also populates the command list with commands to be issued to
     * clients.
     * @param effect the effect to resolve
     * @param cmsg the command message
     * @return true if follow up action must be taken
     */
    private boolean resolveEffect(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        AHLogger.debug("resolveEffect", "resolving : " + effect.getType() + "," + effect.getDetails());
        /* Types of effects coded
         * 1. Investigator Gains / Loses
         * 2. Damage
         * 3. Investigator Effect
         * 4. Instant Status
         * 5. Heal / Heal in Location
         * 6. Last Item Used
         * 7. Recharge
         * 8. Defeat Monster
         * 9. Arkham Encounter
         * 10. Teleport
         * 11. Other World Encounter
         * 12. Monster Appears
         *
         * Global type effects
         * 1. Global Variable Modification
         * 2. Mythos Gate Surge
         * 3. Location Clue
         * 4. Spawn Monsters / Return Monsters
         * 5. Rampaging Monsters
         * 6. New Global Effect
         */
        if (effect.getType().equals("None") && effect.getDetails().equals("No Encounter"))
        {
            return false;
        }
        if (effect.getType().equals("Investigator Gains"))
        {
            return resolveInvestigatorGains(evstate, cmsg);
        }
        else if (effect.getType().equals("Investigator Effect"))
        {
            return resolveInvestigatorEffect(evstate, cmsg);
        }
        else if (effect.getType().equals("Investigator Loses"))
        {
            return resolveInvestigatorLoses(evstate, cmsg);
        }
        else if (effect.getType().equals("Instant Status"))
        {
            return resolveInstantStatus(evstate, cmsg);
        }
        else if (effect.getType().equals("Global Variable Modification"))
        {
            return resolveGlobalVariableMod(evstate, cmsg);
        }
        else if (effect.getType().equals("Mythos Gate Surge"))
        {
            return resolveMythosGateSurge(evstate, cmsg);
        }
        else if (effect.getType().equals("Location Clue"))
        {
            return resolveLocationClue(evstate, cmsg);
        }
        else if (effect.getType().equals("Spawn Monsters"))
        {
            return resolveSpawnMonsters(evstate, cmsg);
        }
        else if (effect.getType().equals("Return Monsters"))
        {
            return resolveReturnMonsters(evstate, cmsg);
        }
        else if (effect.getType().equals("Rampaging Monsters"))
        {
            return resolveRampagingMonsters(evstate, cmsg);
        }
        else if (effect.getType().equals("New Global Effect"))
        {
            return resolveNewGlobalEffect(evstate, cmsg);
        }
        else if (effect.getType().equals("Damage"))
        {
            return resolveDamage(evstate, cmsg);
        }
        else if (effect.getType().equals("Heal") || effect.getType().equals("Heal in Location"))
        {
            return resolveHeal(evstate, cmsg);
        }
        else if (effect.getType().equals("Last Item Used"))
        {
            return resolveLastItemUsed(evstate, cmsg);
        }
        else if (effect.getType().equals("Recharge"))
        {
            return resolveRecharge(evstate, cmsg);
        }
        else if (effect.getType().equals("Monster Appears"))
        {
            return resolveMonsterAppears(evstate, cmsg);
        }
        else if (effect.getType().equals("Defeat Monster"))
        {
            return resolveDefeatMonster(evstate, cmsg);
        }
        else if (effect.getType().equals("Arkham Encounter"))
        {
            return resolveArkhamEncounter(evstate, cmsg);
        }
        else if (effect.getType().equals("Other World Encounter"))
        {
            return resolveOtherWorldEncounter(evstate, cmsg);
        }
        else if (effect.getType().equals("Teleport"))
        {
            return resolveTeleport(evstate, cmsg);
        }
        else
        {
            AHLogger.warning("resolveEffect", effect.getType() + " type not handled yet.");
        }

        return false;
    }

    /**
     * Resolves the "Investigator Gains" effect type
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveInvestigatorGains(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        String amount = effect.getAmount();
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        AmountParser apar = new AmountParser(amount);
        int index = inv.getIndex();
        if (InvestigatorCardType.isValidCardType(details))
        {
            switch (apar.type)
            {
            case NONE:
                Deck <? extends InvestigatorCard> deck = gameData.getDeck(details);
                if (evstate.isDefined("!Invcardchoice"))
                {
                    String items = (String)evstate.getProperty("!Invcardchoice");
                    String[] tokens = items.split(",");
                    List<? extends InvestigatorCard> templist = deck.getTempList();
                    StringBuffer sb = new StringBuffer();
                    StringBuffer strBuf = new StringBuffer();
                    GameData.reorderById(templist, tokens);
                    for (int i = 0; i < apar.m; i++)
                    {
                        InvestigatorCard card = deck.getTempList().remove(0);
                        giveInvestigatorCard(inv, card);
                        if (i != 0)
                        {
                            sb.append(",");
                            strBuf.append(",");
                        }
                        sb.append(tokens[i]);
                        strBuf.append(card.getName());
                    }
                    deck.discardTempList();
                    cmsg.addCommand("#Invcard", sb.toString(), index);
                    displayMetaInfo(inv.getPlayer(), inv.getName() + " has received: " +
                        strBuf.toString());
                    return false;
                }
                /* Check if the 'Extra Choices' ability affects anything */
                List<ModEffect> modlist = inv.searchModifiers("Extra Choices", details);
                for (ModEffect mod : modlist)
                {
                    //TODO: handle non-always cases
                    if (mod.getUsage() != EffectUsage.ALWAYS)
                    {
                        continue;
                    }
                    apar.n += XmlTools.parseInt(mod.getAmount());
                }

                if (!evstate.isDefined("!Scrounge") &&
                        inv.hasModifier("Investigator Special", "Scrounge") &&
                        (details.equals("Common") || details.equals("Unique") ||
                         details.equals("Spell")))
                {
                    /* Check if the investigator can scrounge */
                    String s = details + "," + apar.n;
                    cmsg.addCommand("!Scrounge", s, index);
                    evstate.addProperty("!Scrounge", s);
                    eventStack.push(evstate);
                    return true;
                }

                StringBuffer sb = new StringBuffer();
                StringBuffer strBuf = new StringBuffer();
                if (evstate.isDefined("!Scrounge"))
                {
                    /* If the investigator can scrounge, then
                     * the cards are already selected
                     */
                    String prop = (String)evstate.getProperty("!Scrounge");
                    String[] tokens = prop.split(",", 3);
                    sb.append(tokens[2]);

                }
                else
                {
                    for (int i = 0; i < apar.n; i++)
                    {
                        InvestigatorCard card = deck.tempDeal();
                        if (sb.length() > 0) sb.append(",");
                        sb.append(card.getId());
                    }
                }
                if (apar.n == apar.m)
                {
                    cmsg.addCommand("#Invcard", sb.toString(), index);
                    // actually give the cards
                    while (deck.getTempList().size() > 0)
                    {
                        InvestigatorCard card = deck.getTempList().remove(0);
                        giveInvestigatorCard(inv, card);
                        if (strBuf.length() != 0)
                        {
                            strBuf.append(", ");
                        }
                        strBuf.append(card.getName());
                    }
                    displayMetaInfo(inv.getPlayer(), inv.getName() + " has received: " +
                            strBuf.toString());
                    return false;
                }
                else
                {
                    cmsg.addCommand("!Invcardchoice", apar.m + "," + sb.toString(), index);
                    eventStack.push(evstate);
                    return true;
                }
            default:
                break;
            }
        }
        else if (details.equals("Clue"))
        {
            int val = 0;
            switch (apar.type)
            {
            case NONE:
                val = apar.n;
                break;
            case STAMINA:
                val = inv.getCurrentStamina();
                break;
            case SANITY:
                val = inv.getCurrentSanity();
                break;
            }
            if (val != 0)
            {
                //TODO: Add responses to receiving clues
                inv.modifyClues(val);
                cmsg.addCommand("#AddClue", String.valueOf(val), inv.getIndex());
                addMessageCommand(cmsg, inv.getName() + " has received " + val
                        + " clues.", inv.getIndex());
            }
        }
        else if (details.equals("Money"))
        {
            int val = 0;
            switch (apar.type)
            {
            case NONE:
                val = apar.n;
                break;
            case STAMINA:
                val = inv.getCurrentStamina();
                break;
            case SANITY:
                val = inv.getCurrentSanity();
                break;
            }
            if (val != 0)
            {
                //TODO: Add responses to receiving money
                inv.modifyMoney(val);
                cmsg.addCommand("#AddMoney", String.valueOf(val), inv.getIndex());
                addMessageCommand(cmsg, inv.getName() + " has received $" + val +
                        ".", evstate.getPlayer());
            }
        }
        else if (details.equals("Monster Trophy"))
        {
            if (apar.type != SpecialAmountType.NONE)
            {
                AHLogger.warning("resolveInvestigatorGains", "Invalid amount type specified for" +
                		" gain Monster Trophy");
                return false;
            }
            int val = apar.n;
            for (int i = 0; i < val; i++)
            {
                Monster mon = gameData.drawRandomMonster();
                inv.getMonsterTrophies().add(mon);
                cmsg.addCommand("#DefeatMonster", mon.getId() + ",true");
                addMessageCommand(cmsg, inv.getName() + " has obtained a " + mon.getName() +
                        " monster trophy.", evstate.getPlayer());
            }
        }
        else if (details.startsWith("Specific Card:"))
        {
            String cardName = details.substring(14);
            InvestigatorCard card = gameData.dealFirstSpecificCard(cardName);
            if (card != null)
            {
                inv.gainInvestigatorCard(card);
                cmsg.addCommand("#Invcard", card.getId() + "", index);
            }
            else
            {
                cmsg.addCommand("#Message", "There are no more " + cardName + " in " +
                        "the deck for " + inv.getName(), index);
            }
        }
        else if (details.startsWith("Common:First Weapon"))
        {
            Deck<CommonItem> deck = gameData.getCommonDeck();
            if (evstate.isDefined("!Invcardchoice"))
            {
                String items = (String)evstate.getProperty("!Invcardchoice");
                String[] tokens = items.split(",");
                List<? extends InvestigatorCard> templist = deck.getTempList();
                StringBuffer sb = new StringBuffer();
                StringBuffer strBuf = new StringBuffer();
                GameData.reorderById(templist, tokens);
                for (int i = 0; i < apar.m; i++)
                {
                    InvestigatorCard card = deck.getTempList().remove(0);
                    giveInvestigatorCard(inv, card);
                    if (i != 0)
                    {
                        sb.append(",");
                        strBuf.append(",");
                    }
                    sb.append(tokens[i]);
                    strBuf.append(card.getName());
                }
                deck.discardTempList();
                cmsg.addCommand("#Invcard", sb.toString(), index);
                displayMetaInfo(inv.getPlayer(), inv.getName() + " has received: " +
                    strBuf.toString());
                return false;
            }
            /* Check if the 'Extra Choices' ability affects anything */
            List<ModEffect> modlist = inv.searchModifiers("Extra Choices", details);
            for (ModEffect mod : modlist)
            {
                //TODO: handle non-always cases
                if (mod.getUsage() != EffectUsage.ALWAYS)
                {
                    continue;
                }
                apar.n += XmlTools.parseInt(mod.getAmount());
            }

            StringBuffer sb = new StringBuffer();
            StringBuffer strBuf = new StringBuffer();
            List<CommonItem> selectedItems = new ArrayList<CommonItem>();
            for (int i = 0; i < apar.n; i++)
            {
                CommonItem card = deck.deal();
                while (!card.isWeapon() && !deck.isEmpty())
                {
                    deck.getTempList().add(card);
                    card = deck.deal();
                }
                selectedItems.add(card);
                if (sb.length() > 0) sb.append(",");
                sb.append(card.getId());
            }
            deck.discardTempList();
            deck.getTempList().addAll(selectedItems);
            if (deck.getTempList().size() <= apar.m)
            {
                cmsg.addCommand("#Invcard", sb.toString(), index);
                // actually give the cards
                while (deck.getTempList().size() > 0)
                {
                    InvestigatorCard card = deck.getTempList().remove(0);
                    giveInvestigatorCard(inv, card);
                    if (strBuf.length() != 0)
                    {
                        strBuf.append(", ");
                    }
                    strBuf.append(card.getName());
                }
                displayMetaInfo(inv.getPlayer(), inv.getName() + " has received: " +
                        strBuf.toString());
                return false;
            }
            else
            {
                cmsg.addCommand("!Invcardchoice", apar.m + "," + sb.toString(), index);
                eventStack.push(evstate);
                return true;
            }
        }
        else if (details.startsWith("Unique:First Tome"))
        {
            Deck<UniqueItem> deck = gameData.getUniqueDeck();
            if (evstate.isDefined("!Invcardchoice"))
            {
                String items = (String)evstate.getProperty("!Invcardchoice");
                String[] tokens = items.split(",");
                List<? extends InvestigatorCard> templist = deck.getTempList();
                StringBuffer sb = new StringBuffer();
                StringBuffer strBuf = new StringBuffer();
                GameData.reorderById(templist, tokens);
                for (int i = 0; i < apar.m; i++)
                {
                    InvestigatorCard card = deck.getTempList().remove(0);
                    giveInvestigatorCard(inv, card);
                    if (i != 0)
                    {
                        sb.append(",");
                        strBuf.append(",");
                    }
                    sb.append(tokens[i]);
                    strBuf.append(card.getName());
                }
                deck.discardTempList();
                cmsg.addCommand("#Invcard", sb.toString(), index);
                displayMetaInfo(inv.getPlayer(), inv.getName() + " has received: " +
                    strBuf.toString());
                return false;
            }
            /* Check if the 'Extra Choices' ability affects anything */
            List<ModEffect> modlist = inv.searchModifiers("Extra Choices", details);
            for (ModEffect mod : modlist)
            {
                //TODO: handle non-always cases
                if (mod.getUsage() != EffectUsage.ALWAYS)
                {
                    continue;
                }
                apar.n += XmlTools.parseInt(mod.getAmount());
            }

            StringBuffer sb = new StringBuffer();
            StringBuffer strBuf = new StringBuffer();
            List<UniqueItem> selectedItems = new ArrayList<UniqueItem>();
            for (int i = 0; i < apar.n; i++)
            {
                UniqueItem card = deck.deal();
                while (!card.getSubtype().equals(UniqueItemSubtype.TOME) && !deck.isEmpty())
                {
                    deck.getTempList().add(card);
                    card = deck.deal();
                }
                selectedItems.add(card);
                if (sb.length() > 0) sb.append(",");
                sb.append(card.getId());
            }
            deck.discardTempList();
            deck.getTempList().addAll(selectedItems);
            if (deck.getTempList().size() <= apar.m)
            {
                cmsg.addCommand("#Invcard", sb.toString(), index);
                // actually give the cards
                while (deck.getTempList().size() > 0)
                {
                    InvestigatorCard card = deck.getTempList().remove(0);
                    giveInvestigatorCard(inv, card);
                    if (strBuf.length() != 0)
                    {
                        strBuf.append(", ");
                    }
                    strBuf.append(card.getName());
                }
                displayMetaInfo(inv.getPlayer(), inv.getName() + " has received: " +
                        strBuf.toString());
                return false;
            }
            else
            {
                cmsg.addCommand("!Invcardchoice", apar.m + "," + sb.toString(), index);
                eventStack.push(evstate);
                return true;
            }
        }
        else
        {
            AHLogger.warning("resolveEffect", "Investigator Gains: " + details +
                    " not handled yet.");
            return false;
        }
        return false;
    }

    /**
     * Resolves the "Investigator Effect" effect type
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveInvestigatorEffect(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());

        if (effect.getAmount() != null && effect.getAmount().equals("-1"))
        {
            /* Remove the effect instead */
            inv.removeStatus(details);
            cmsg.addCommand("#LoseStatus", details);
            addMessageCommand(cmsg, inv.getName() + " has lost " + details + ".", inv.getIndex());
            return false;
        }

        if (details.equals("Blessing"))
        {
            PersonalEffect peff = inv.removeStatus("Curse");
            if (peff == null)
            {
                inv.applyStatus(gameData.createPersonalEffect("Blessing"));
                addMessageCommand(cmsg, inv.getName() + " has gained " + details + ".", inv.getIndex());
                cmsg.addCommand("#GainStatus", details);
            }
            else
            {
                cmsg.addCommand("#LoseStatus", "Curse");
                addMessageCommand(cmsg, inv.getName() + " has lost Curse.", inv.getIndex());
            }
        }
        else if (details.equals("Curse"))
        {
            PersonalEffect peff = inv.removeStatus("Blessing");

            if (peff == null)
            {
                inv.applyStatus(gameData.createPersonalEffect("Curse"));
                addMessageCommand(cmsg, inv.getName() + " has gained " + details + ".",
                        inv.getIndex());
                cmsg.addCommand("#GainStatus", details);
            }
            else
            {
                cmsg.addCommand("#LoseStatus", "Blessing");
                addMessageCommand(cmsg, inv.getName() + " has lost Blessing.", inv.getIndex());
            }
        }
        else if (details.equals("Retainer") ||
                details.equals("Silver Twilight Lodge Membership") ||
                details.equals("Voice of Ra Bonus"))
        {
            // for these kinds of effects, add the status if not already present
            if (GameData.retrieveByName(inv.getStatusEffects(), details) == null)
            {
                inv.applyStatus(gameData.createPersonalEffect(details));
                cmsg.addCommand("#GainStatus", details);
                addMessageCommand(cmsg, inv.getName() + " has gained " + details + ".",
                        inv.getIndex());
            }
        }
        return false;
    }

    /**
     * Resolves the "Investigator Loses" effect type
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveInvestigatorLoses(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        int index = evstate.getPlayer();
        Investigator inv = gameData.getInvestigator(index);
        AmountParser apar = new AmountParser(effect.getAmount());
        if (details.equals("Money"))
        {
            int val = apar.getNumericAmount(inv.getMoney());
            inv.modifyMoney(-val);
            cmsg.addCommand("#AddMoney", String.valueOf(-val), index);
            return false;
        }
        else if (details.equals("Clue"))
        {
            int val = apar.getNumericAmount(inv.getClues());
            inv.modifyClues(-val);
            cmsg.addCommand("#AddClues", String.valueOf(-val), index);
            return false;
        }
        else if (details.equals("Monster Trophy"))
        {
            //TODO: resolve this
            return false;
        }
        else if (details.equals("Gate Trophy"))
        {
            //TODO: resolve this
            return false;
        }
        else if (details.startsWith("Choice:"))
        {
            //TODO: resolve this
            return false;
        }
        // Gather items
        List<InvestigatorCard> list = new ArrayList<InvestigatorCard>();
        ItemFilter filter = null;
        String indicator = "";
        StringBuffer sb = new StringBuffer();
        StringBuffer namebuf = new StringBuffer();

        if (details.startsWith("Specific Card:"))
        {
            final String name = details.substring(14);
            indicator = name;
            filter = new ItemFilter(){
                public boolean matches(InvestigatorCard card)
                {
                    return name.equals(card.getName());
                }
            };
        }
        else if (details.equals("Item"))
        {
            indicator = "items";
            filter = new ItemFilter(){
                public boolean matches(InvestigatorCard card)
                {
                    return card.isItem();
                }
            };
        }
        else if (InvestigatorCardType.isValidCardType(details))
        {
            final InvestigatorCardType type = InvestigatorCardType.getCardType(details);
            indicator = type.toString() + "s";
            filter = new ItemFilter(){
                public boolean matches(InvestigatorCard card)
                {
                    return card.getType().equals(type);
                }
            };
        }
        else if (details.equals("Weapon"))
        {
            indicator = "weapons";
            filter = new ItemFilter(){
                public boolean matches(InvestigatorCard card)
                {
                    switch (card.getType())
                    {
                    case COMMON:
                        return ((CommonItem)card).isWeapon();
                    case UNIQUE:
                        return ((UniqueItem)card).isWeapon();
                    default:
                        return false;
                    }
                }
            };
        }
        else if (details.equals("Spell/Weapon"))
        {
            indicator = "spells or weapons";
            filter = new ItemFilter(){
                public boolean matches(InvestigatorCard card)
                {
                    switch (card.getType())
                    {
                    case COMMON:
                        return ((CommonItem)card).isWeapon();
                    case UNIQUE:
                        return ((UniqueItem)card).isWeapon();
                    case SPELL:
                        return true;
                    default:
                        return false;
                    }
                }
            };
        }
        for (InvestigatorCard card : inv.getInvcards())
        {
            if (filter.matches(card))
            {
                list.add(card);
            }
        }
        int val = apar.getNumericAmount(list.size());
        if (val == 0)
        {
            return false;
        }
        if (list.size() == 0)
        {
            addMessageCommand(cmsg, inv.getName() + " has no " + indicator + " to lose.", index);
            return false;
        }
        if (val >= list.size())
        {
            for (InvestigatorCard card : list)
            {
                inv.loseInvestigatorCard(card);
                sb.append(card.getId() + ",");
                namebuf.append(card.getName() + ",");
                gameData.returnToDeck(card);
            }
            sb.deleteCharAt(sb.length() - 1);
            namebuf.deleteCharAt(namebuf.length() - 1);
            addMessageCommand(cmsg, inv.getName() + " has lost: " + namebuf.toString(), index);
            cmsg.addCommand("#LoseInvcard", sb.toString(), index);
            return false;
        }
        if (evstate.isDefined("!Losecardchoice"))
        {
            String param = (String)evstate.getProperty("!Losecardchoice");
            String[] tokens = param.split(",");
            if (tokens.length != val)
            {
                AHLogger.error("resolveInvestigatorLoses", "Error in client response: invalid " +
                		"number of items.");
                return false;
            }
            for (String tok : tokens)
            {
                int id = XmlTools.parseInt(tok);
                InvestigatorCard card = inv.loseInvestigatorCard(id);
                namebuf.append(card.getName() + ",");
                gameData.returnToDeck(card);
            }
            namebuf.deleteCharAt(namebuf.length() - 1);
            addMessageCommand(cmsg, inv.getName() + " has lost: " + namebuf.toString(), index);
            cmsg.addCommand("#LoseInvcard", param, index);
            return false;
        }
        /* Request the player to select which items to lose */
        for (InvestigatorCard card : list)
        {
            sb.append("," + card.getId());
        }
        cmsg.addCommand("!Losecardchoice", val + sb.toString());
        eventStack.push(evstate);
        return true;
    }

    /**
     * Resolves the "Instant Status" effect. Many instant statuses will result in
     * the investigator being unable to execute the remaining events in the event stack.
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveInstantStatus(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        if (details.equals("Unconscious") || details.equals("Insane"))
        {
            /*
             * 1. Search through event stack and remove all queued events
             *    of the player.
             * 2. Mark player as dead.
             * 3. Enqueue losing half items and losing half clues
             * 4. Send player to appropriate location.
             */
            EventUtils.clearEvents(eventStack, evstate.getPlayer());
            inv.setDead(true);
            EventNode en1 = EventUtils.createEffectNode("Investigator Loses", "Item", "=Half");
            EventNode en2 = EventUtils.createEffectNode("Investigator Loses", "Clue", "=Half");
            eventStack.push(new EventState(en1, EventStateType.NEW_EVENT, evstate.getPlayer()));
            eventStack.push(new EventState(en2, EventStateType.NEW_EVENT, evstate.getPlayer()));
            if (!inv.getLocation().isArkhamLocation())
            {
                if (details.equals("Unconscious"))
                {
                    inv.setStamina(1);
                    cmsg.addCommand("#SetStamina", "1", evstate.getPlayer());
                }
                else
                {
                    inv.setSanity(1);
                    cmsg.addCommand("#SetSanity", "1", evstate.getPlayer());
                }
                gameData.moveInvestigatorTo(inv, gameData.getLocationContainer(Board.LITAS_NAME));
                cmsg.addCommand("#Teleport", Board.LITAS_NAME, evstate.getPlayer());
                EventNode en3 = EventUtils.createEffectNode("Instant Status", "Delayed", null);
                eventStack.push(new EventState(en3, EventStateType.NEW_EVENT, evstate.getPlayer()));
            }
            else
            {
                if (details.equals("Unconscious"))
                {
                    inv.setStamina(1);
                    cmsg.addCommand("#SetStamina", "1", evstate.getPlayer());
                    Location hospital = gameData.getHospitalLocation();
                    gameData.moveInvestigatorTo(inv, hospital);
                    cmsg.addCommand("#Teleport", hospital.getName(), evstate.getPlayer());
                }
                else
                {
                    inv.setSanity(1);
                    cmsg.addCommand("#SetSanity", "1", evstate.getPlayer());
                    Location asylum = gameData.getAsylumLocation();
                    gameData.moveInvestigatorTo(inv, asylum);
                    cmsg.addCommand("#Teleport", asylum.getName(), evstate.getPlayer());
                }
            }
        }
        else if (details.equals("Delayed"))
        {
            // NOTE: Check for Mark Harrigan
            inv.setDelayed(true);
            cmsg.addCommand("#Instant", "Delayed");
        }
        else if (details.equals("LITAS"))
        {
            gameData.moveInvestigatorTo(inv, gameData.getLocationContainer(Board.LITAS_NAME));
            EventNode en1 = EventUtils.createEffectNode("Instant Status", "Delayed", null);
            cmsg.addCommand("#Teleport", Board.LITAS_NAME, evstate.getPlayer());
            eventStack.push(new EventState(en1, EventStateType.NEW_EVENT, evstate.getPlayer()));
        }
        else
        {
            AHLogger.warning("resolveInstantStatus", details + " not implemented yet.");
        }

        return false;
    }

    /**
     * Resolves the "Global Variable Modification" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveGlobalVariableMod(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        String amount = effect.getAmount();
        AmountParser apar = new AmountParser(amount);
        if (details.equals("Doom"))
        {
            //TODO: check for effects that respond to it, eg. Diana Stanley
            if (apar.n > 0)
            {
                for (int i = 0; i < apar.n; i++)
                {
                    gameData.getAncientOne().modifyDoomTokens(1);
                }
                cmsg.addCommand("#AddDoom", amount);
            }
            else
            {
                gameData.getAncientOne().modifyDoomTokens(apar.n);
                cmsg.addCommand("#AddDoom", String.valueOf(apar.n));
            }
            //TODO: Check if ancient one has awakened
        }
        else if (details.equals("Terror"))
        {
            //TODO: check for effects that block it
            for (int i = 0; i < apar.n; i++)
            {
                gameData.raiseTerrorLevel(1);
            }
            cmsg.addCommand("#AddTerror", amount);
        }
        else
        {
            AHLogger.warning("resolveEffect", "Global Variable Mod: " + details +
                    " not handled yet.");
            return false;
        }
        return false;
    }

    /**
     * Resolves the "New Global Effect" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveNewGlobalEffect(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        Environment env = gameData.getEnvironment(details);
        env.initialize();
        gameData.getActiveEnvironments().add(env);
        cmsg.addCommand("#NewEnvironment", details);
        return false;
    }

    /**
     * Resolves the "Mythos Gate Surge" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveMythosGateSurge(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        String amount = effect.getAmount();
        AmountParser apar = new AmountParser(amount);
        List<Location> gateLocList = gameData.getOpenGateLocations();
        int openGates = gateLocList.size();
        int rem = apar.n % openGates;
        if (rem > 1 && !evstate.isDefined("!SurgePlacement"))
        {
            cmsg.addCommand("!SurgePlacement", details + "," + amount, gameData.getStartingPlayer());
            eventStack.push(evstate);
            return true;
        }
        else
        {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < (apar.n / openGates); i++)
            {
                for (int j = 0; j < openGates; j++)
                {
                    Monster mon = spawnMonsterOn(cmsg, gateLocList.get(i));
                    if (sb.length() > 0) sb.append(",");
                    sb.append(gateLocList.get(i) + "," + mon.getId());
                }
            }
            if (rem >= 1)
            {
                Location loc = GameData.retrieveByName(gateLocList, details);
                Monster mon = spawnMonsterOn(cmsg, loc);
                sb.append("," + details + "," + mon.getId());
            }
            if (evstate.isDefined("!SurgePlacement"))
            {
                String[] tokens = ((String)evstate.getProperty("!SurgePlacement")).split(",");
                for (int i = 0; i < tokens.length; i++)
                {
                    Location loc = GameData.retrieveByName(gateLocList, tokens[i]);
                    Monster mon = spawnMonsterOn(cmsg, loc);
                    sb.append("," + details + "," + mon.getId());
                }
            }
            cmsg.addCommand("Mythos Gate Surge", sb.toString());
        }
        return false;
    }

    /**
     * Resolves the "Location Clue" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveLocationClue(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        Location loc = gameData.getLocation(details);
        List<Investigator> invlist = loc.getInvestigatorList();
        if (invlist.size() == 0)
        {
            cmsg.addCommand("#LocationClue", details);
            loc.addClue();
        }
        else if (invlist.size() == 1)
        {
            Investigator inv = invlist.get(0);
            EventNode en = EventUtils.createEffectNode("Investigator Gains", "Clue", "1");
            EventState es = new EventState(en, EventStateType.NEW_EVENT, inv.getIndex());
            es.setMessage(inv.getName() + " has found a clue which appeared at " + loc.getName() + ".");
            eventStack.push(es);
        }
        else if (invlist.size() > 1)
        {
            if (evstate.isDefined("!AllocateClue"))
            {
                Investigator inv = gameData.getInvestigatorByName(
                        (String)evstate.getProperty("!AllocateClue"));
                EventNode en = EventUtils.createEffectNode("Investigator Gains", "Clue", "1");
                eventStack.push(new EventState(en, EventStateType.NEW_EVENT, inv.getIndex()));
                return false;
            }
            else
            {
                cmsg.addCommand("!AllocateClue", details, gameData.getStartingPlayer());
                eventStack.push(evstate);
                return true;
            }
        }

        return false;
    }

    /**
     * Resolves the "Spawn Monsters" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveSpawnMonsters(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        AmountParser apar = new AmountParser(effect.getAmount());
        Location loc = gameData.getLocation(details);
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < apar.n; i++)
        {
            Monster mon = spawnMonsterOn(cmsg, loc);
            sb.append("," + mon.getId());
        }
        addMessageCommand(cmsg, apar.n + " monsters have spawned on " + loc.getName() + ".", -1);
        cmsg.addCommand("#SpawnMonster", details + sb.toString());
        return false;
    }

    /**
     * Resolves the "Return Monsters" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveReturnMonsters(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        gameData.returnMonstersOn(details);
        cmsg.addCommand("#ReturnMonsters", details);
        return false;
    }

    /**
     * Resolves the "Rampaging Monsters" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveRampagingMonsters(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        boolean result = gameData.returnMonstersNamed(details);
        cmsg.addCommand("#RampagingMonsters", details);
        if (result)
        {
            addMessageCommand(cmsg, "The terror level has risen because at least " +
                    "one monster was returned!", -1);
            EventNode en = EventUtils.createEffectNode("Global Variable Modification", "Terror", "1");
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT));
        }
        return false;
    }

    /**
     * Resolves the "Heal" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveHeal(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        AmountParser apar = new AmountParser(effect.getAmount());
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        boolean healOther = effect.getType().equals("Heal in Location");
        int amt = 0;

        if (healOther)
        {
            if (evstate.isDefined("!HealTarget"))
            {
                inv = gameData.getInvestigatorByName((String)evstate.getProperty("!HealTarget"));
            }
            else
            {
                List<Investigator> invlist = inv.getLocation().getInvestigatorList();
                int count = 0;
                StringBuffer sb = new StringBuffer();
                for (Investigator investigator : invlist)
                {
                    if (investigator.getCurrentStamina() < investigator.getMaxStaminaMod())
                    {
                        if (sb.length() > 0)
                        {
                            sb.append(",");
                        }
                        sb.append(investigator.getName());
                        count++;
                    }
                }
                if (count > 1)
                {
                    cmsg.addCommand("!HealTarget", sb.toString());
                    eventStack.push(evstate);
                    return true;
                }
            }
        }

        if (details.equals("Stamina"))
        {
            amt = apar.getNumericAmount(inv.getMaxStaminaMod());
            if (apar.type.equals(SpecialAmountType.SUCCESSES))
            {
                //TODO: deal with =Successes
            }
            if (amt > 0 && inv.getCurrentStamina() < inv.getMaxStaminaMod())
            {
                int val = inv.modifyStamina(amt);
                addMessageCommand(cmsg, inv.getName() + " has healed " + val +
                        " Stamina.", evstate.getPlayer());
                cmsg.addCommand("#Heal", "Stamina," + amt, evstate.getPlayer());
            }
        }
        else if (details.equals("Sanity"))
        {
            amt = apar.getNumericAmount(inv.getMaxSanityMod());
            if (apar.type.equals(SpecialAmountType.SUCCESSES))
            {
                //TODO: deal with =Successes
            }
            if (amt > 0 && inv.getCurrentSanity() < inv.getMaxSanityMod())
            {
                int val = inv.modifySanity(amt);
                addMessageCommand(cmsg, inv.getName() + " has healed " + val +
                        " Sanity.", evstate.getPlayer());
                cmsg.addCommand("#Heal", "Sanity," + amt, evstate.getPlayer());
            }
        }

        return false;
    }

    /**
     * Resolves the "Last Item Used" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveLastItemUsed(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        if (inv.getCurrentUsedEffect() == null)
        {
            AHLogger.warning("resolveLastItemUsed", inv.getName() + " has no last used " +
            		"effect defined.");
            return false;
        }
        UsableEffect ueff = inv.getCurrentUsedEffect();
        if (details.equals("Exhaust"))
        {
            ueff.getSource().setExhausted(true);
            cmsg.addCommand("#ExhaustEffect", ueff.getSourceString());
        }
        else if (details.equals("Discard"))
        {
            if (ueff.getSource() instanceof InvestigatorCard)
            {
                InvestigatorCard card = (InvestigatorCard)ueff.getSource();
                inv.loseInvestigatorCard(card);
                gameData.returnToDeck(card);
                cmsg.addCommand("#LoseInvcard", String.valueOf(card.getId()));
            }
            else if (ueff.getSource() instanceof PersonalEffect)
            {
                PersonalEffect peff = (PersonalEffect)ueff.getSource();
                inv.removeStatus(peff);
                cmsg.addCommand("#LoseStatus", peff.getName());
            }
        }
        else if (details.equals("Add Token"))
        {
            gameData.addTokensToEffectSource(inv, ueff.getSource(), 1);
            cmsg.addCommand("#AddToken", ueff.getSourceString());
        }

        return false;
    }

    /**
     * Resolves the "Damage" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveDamage(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        AmountParser apar = new AmountParser(effect.getAmount());
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());

        if (details.equals("Stamina"))
        {
            List<ModEffect> stamProtList = inv.searchModifiers("Prevent Damage", "Stamina");
            /* Check for damage prevention:
             * 1. If there are only "Always" modifiers, apply them first.
             * 2. If there is outstanding damage, raise a "Modifier Usage" event
             */
            int damage = apar.getNumericAmount(inv.getCurrentStamina());
            int reduction = 0;
            if (evstate.isDefined("PreventStamDamage"))
            {
                reduction = (Integer)evstate.getProperty("PreventStamDamage");
            }
            boolean modsUsed = false;
            for (int i = 0; i < stamProtList.size(); i++)
            {
                ModEffect modeff = stamProtList.get(i);
                if (modeff.getUsage().equals(EffectUsage.ALWAYS))
                {
                    reduction += modeff.getAmountAsInt();
                    stamProtList.remove(i);
                    i--;
                }
                else if (modeff.isExhausted())
                {
                    stamProtList.remove(i);
                    i--;
                }
            }
            if (evstate.isModEffectsSelected())
            {
                if (!evstate.getModEffects().isEmpty())
                {
                    ModEffect mod = evstate.getModEffects().get(0);
                    AmountParser apar2 = new AmountParser(mod.getAmount());
                    int moreReduction = apar2.getNumericAmount(damage);
                    if (mod.getUsage().equals(EffectUsage.LIMITED_MULTI))
                    {
                        Integer numUses = (Integer)evstate.getProperty(mod.getIdentifier());
                        moreReduction *= numUses;
                        addMessageCommand(cmsg, inv.getName() + " uses " +
                                mod.getSource().getName() + " " + numUses +
                                " times to prevent damage.", evstate.getPlayer());

                    }
                    else
                    {
                        addMessageCommand(cmsg, inv.getName() + " uses " +
                                mod.getSource().getName() + " to prevent damage.",
                                evstate.getPlayer());
                    }
                    damage = Math.max(damage - moreReduction, 0);
                    /* Modifier applied, so set the damage reduction property */
                    evstate.modifyIntegerProperty("PreventStamDamage", moreReduction);
                }
                else
                {
                    modsUsed = true;
                }
            }
            damage -= reduction;
            if (damage <= 0)
            {
                AHLogger.info("resolveDamage", "No damage dealt to " + inv.getName());
                return false;
            }
            // Always remember to remove unavailable mod effects
            GameData.removeUnavailableEffects(inv, stamProtList);
            if (stamProtList.size() == 0 || modsUsed)
            {
                cmsg.addCommand("#Damage", "Stamina," + damage, evstate.getPlayer());
                inv.modifyStamina(-damage);
                displayMetaInfo(inv.getPlayer(), inv.getName() +
                        " lost " + damage + " Stamina.");
                if (inv.getCurrentStamina() <= 0)
                {
                    /* Investigator is unconscious */
                    EventNode en = EventUtils.createEffectNode("Instant Status", "Unconscious", null);
                    eventStack.push(new EventState(en, EventStateType.NEW_EVENT, evstate.getPlayer()));
                }
                return false;
            }
            else
            {
                EventState es = new EventState(stamProtList, EventStateType.MODIFIER_USAGE,
                        evstate.getPlayer());
                es.setPrevState(evstate);
                cmsg.addCommand("!PreventDamage", "Stamina," + damage, evstate.getPlayer());
                eventStack.push(es);
                return true;
            }
        }
        else if (details.equals("Sanity"))
        {
            List<ModEffect> sanProtList = inv.searchModifiers("Prevent Damage", "Sanity");
            /* Check for damage prevention:
             * 1. If there are only "Always" modifiers, apply them first.
             * 2. If there is outstanding damage, raise a "Modifier Usage" event
             */
            int damage = apar.getNumericAmount(inv.getCurrentSanity());
            int reduction = 0;
            if (evstate.isDefined("PreventSanDamage"))
            {
                reduction = (Integer)evstate.getProperty("PreventSanDamage");
            }
            boolean modsUsed = false;
            for (int i = 0; i < sanProtList.size(); i++)
            {
                ModEffect modeff = sanProtList.get(i);
                if (modeff.getUsage().equals(EffectUsage.ALWAYS))
                {
                    reduction += modeff.getAmountAsInt();
                    sanProtList.remove(i);
                    i--;
                }
                else if (modeff.isExhausted())
                {
                    sanProtList.remove(i);
                    i--;
                }
            }
            if (evstate.isModEffectsSelected())
            {
                if (!evstate.getModEffects().isEmpty())
                {
                    ModEffect mod = evstate.getModEffects().get(0);
                    AmountParser apar2 = new AmountParser(mod.getAmount());
                    int moreReduction = apar2.getNumericAmount(damage);
                    if (mod.getUsage().equals(EffectUsage.LIMITED_MULTI))
                    {
                        Integer numUses = (Integer)evstate.getProperty(mod.getIdentifier());
                        moreReduction *= numUses;
                        addMessageCommand(cmsg, inv.getName() + " uses " +
                                mod.getSource().getName() + " " + numUses +
                                " times to prevent damage.", evstate.getPlayer());

                    }
                    else
                    {
                        addMessageCommand(cmsg, inv.getName() + " uses " +
                                mod.getSource().getName() + " to prevent damage.",
                                evstate.getPlayer());
                    }
                    damage = Math.max(damage - moreReduction, 0);
                    /* Modifier applied, so set the damage reduction property */
                    evstate.modifyIntegerProperty("PreventSanDamage", moreReduction);
                }
                else
                {
                    modsUsed = true;
                }
            }
            damage -= reduction;
            if (damage <= 0)
            {
                AHLogger.info("resolveDamage", "No damage dealt to " + inv.getName());
                return false;
            }
            GameData.removeUnavailableEffects(inv, sanProtList);
            if (sanProtList.size() == 0 || modsUsed)
            {
                cmsg.addCommand("#Damage", "Sanity," + damage, evstate.getPlayer());
                inv.modifySanity(-damage);
                displayMetaInfo(inv.getPlayer(), inv.getName() +
                        " lost " + damage + " Sanity.");
                if (inv.getCurrentSanity() <= 0)
                {
                    /* Investigator is insane */
                    EventNode en = EventUtils.createEffectNode("Instant Status", "Insane", null);
                    eventStack.push(new EventState(en, EventStateType.NEW_EVENT, evstate.getPlayer()));
                }
                return false;
            }
            else
            {
                EventState es = new EventState(sanProtList, EventStateType.MODIFIER_USAGE,
                        evstate.getPlayer());
                es.setPrevState(evstate);
                cmsg.addCommand("!PreventDamage", "Sanity," + damage, evstate.getPlayer());
                eventStack.push(es);
                return true;
            }

        }
        else if (details.equals("Stamina and Sanity"))
        {
            List<ModEffect> stamProtList = inv.searchModifiers("Prevent Damage", "Stamina");
            List<ModEffect> sanProtList = inv.searchModifiers("Prevent Damage", "Sanity");

        }
        else if (details.equals("Stamina/Sanity"))
        {
            /*
             * First decide on the split then check for damage prevention.
             */
        }
        else if (details.equals("Sanity Gain Clues"))
        {
            List<ModEffect> sanProtList = inv.searchModifiers("Prevent Damage", "Sanity");
        }
        return false;
    }

    /**
     * Resolves the "Recharge" effect
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveRecharge(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        AmountParser apar = new AmountParser(effect.getAmount());
        if (!details.equals("Movement"))
        {
            AHLogger.warning("resolveRecharge", "Unknown recharge details: " + details);
            return false;
        }
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        if (apar.n <= 0)
        {
            AHLogger.warning("resolveRecharge", "Negative recharge amount: " + apar.n);
            return false;
        }
        if (!inv.areMovementPointsLocked())
        {
            addMessageCommand(cmsg, inv.getName() + " has recharged " + apar.n + " movement points.",
                    evstate.getPlayer());
            cmsg.addCommand("#Recharge", String.valueOf(apar.n), evstate.getPlayer());
            inv.setMovementPoints(inv.getMovementPoints() + apar.n);
        }
        else
        {
            addMessageCommand(cmsg, inv.getName() + " cannot gain any movement points.",
                    evstate.getPlayer());
        }
        return false;
    }

    /**
     * Resolves the "Monster Appears" effect. The details specified is either
     * "Random" for a random monster, or the monster's name for a specific
     * monster.
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveMonsterAppears(EventState evstate, CommandMessage cmsg)
    {
        /*
         * Select the random monster, then push an ENCOUNTER_MONSTER onto the
         * stack.
         */
        EffectType effect = evstate.getEffect();
        String details = effect.getDetails();
        Monster mon = null;
        if (details.equals("Random"))
        {
            mon = gameData.getRandomMonster();

        }
        else
        {
            mon = gameData.getMonsterByName(details);
            if (mon == null)
            {
                AHLogger.error("resolveMonsterAppears", "No " + details + " left in "
                        + "monster cup.");
                return false;
            }
        }
        EventState es = new EventState(mon, EventStateType.ENCOUNTER_MONSTER, evstate.getPlayer());
        eventStack.push(es);
        return false;
    }

    /**
     * Resolves the "Defeat Monster" effect. The details specified is the id of
     * the monster defeated.
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveDefeatMonster(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        int id = XmlTools.parseInt(effect.getDetails());
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        Monster mon = gameData.getMonsterById(id);
        if (mon.getLocation() != null)
        {
            GameData.removeById(mon.getLocation().getMonsterList(), id);
        }
        // remove from the monster cup, if inside
        gameData.drawMonsterById(id);
        if (effect.getAmount().equals("1"))
        {
            inv.getMonsterTrophies().add(mon);
            cmsg.addCommand("#DefeatMonster", effect.getDetails() + ",true", evstate.getPlayer());
        }
        else
        {
            // Monster is endless, no trophy
            cmsg.addCommand("#DefeatMonster", effect.getDetails() + ",false", evstate.getPlayer());
        }
        return false;
    }

    /**
     * Resolves the "Arkham Encounter" effect.
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveArkhamEncounter(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        int index = evstate.getPlayer();
        /* Arkham Encounter:
         * 1. Check for investigator being on an open gate
         *    a. If yes, and the investigator does not have an explored
         *       marker, send him to other world.
         *    b. If yes, and the investigator has an explored marker,
         *       proceed to make a close gate check.
         * 2. Check if !Encounter is defined. If defined move to 3
         *    a. If not defined, check for alternate event. Prompt if
         *       conditions are possible. Set !Encounter to %ALT if
         *       alternate event is selected.
         *    b. Check for Inner Sanctum, and for Extra Choices power.
         *       Make a list of possible choices and return to player.
         *       Player selects and it will be stored under !Encounter
         * 3. If !Encounter is not %ALT, check for Redraw Location Card
         *    modeffect. Prompt if necessary.
         *    a. If chosen, draw another encounter card. Repeat step 2.
         * 4. Finally, execute the encounter.
         */
        Location loc = gameData.getLocation(effect.getDetails());
        String locname = loc.getName();
        if (loc.hasOpenGate())
        {
            if (inv.hasExploredMarker())
            {
                /* Prompt the user to close gate */
                if (!evstate.isDefined("!CloseGateMethod"))
                {
                    boolean elderSign = inv.hasModifier("Special", "Elder Sign");
                    cmsg.addCommand("!CloseGateMethod", String.valueOf(elderSign),
                            index);
                    eventStack.push(evstate);
                    return true;
                }
                String method = (String)evstate.getProperty("!CloseGateMethod");
                GateTile gatetile = loc.getGate();
                if (method.equals("Elder Sign"))
                {
                    /* NOTE: In this special case, the elder sign will be consumed as
                     * part of the effect resolution rather than in the modifier_usage
                     * event.
                     *
                     * This routine will consume the elder sign, seal the gate,
                     * reduce the doom track by 1, then damage 1 sanity and stamina.
                     */
                    List<ModEffect> modlist = inv.searchModifiers("Special", "Elder Sign");
                    if (modlist.isEmpty())
                    {
                        AHLogger.error("resolveArkhamEncounter", inv.getName() + " has no " +
                                "elder sign to use!");
                        return false;
                    }
                    ModEffect esign = modlist.get(0);
                    gameData.consumeModEffect(inv, esign);
                    cmsg.addCommand("#Consume", esign.getIdentifier(), index);
                    EventNode en1 = EventUtils.createEffectNode("Close Gate", locname, "1");
                    EventState es1 = new EventState(en1, EventStateType.NEW_EVENT, index);
                    EventNode en2 = EventUtils.createEffectNode("Global Variable Modification",
                            "Doom", "-1");
                    EventState es2 = new EventState(en2, EventStateType.NEW_EVENT, -1);
                    EventNode en3 = EventUtils.createEffectNode("Damage", "Stamina and Sanity", "1");
                    EventState es3 = new EventState(en3, EventStateType.NEW_EVENT, index);
                    eventStack.push(es3);
                    eventStack.push(es2);
                    eventStack.push(es1);
                    return false;
                } // end of elder sign
                else if (method.equals("Lore") || method.equals("Fight"))
                {
                    int diff = gameData.getGateClosingDifficulty(gatetile);
                    int rollmod = gatetile.getModifier();
                    int cluesToSeal = gameData.getNumCluesToSeal();
                    EventNode en = EventUtils.createGateCloseNode(locname, method, rollmod,
                            diff, cluesToSeal);
                    EventState es = new EventState(en, EventStateType.NEW_EVENT, index);
                    eventStack.push(es);
                    return false;
                }
            }
            else
            {
                GateTile gate = loc.getGate();
                OtherWorld ow = gameData.getOtherWorld(gate.getName());
                gameData.moveInvestigatorTo(inv, ow.getSegment(1));
                cmsg.addCommand("#Teleport", ow.getSegmentName(1));
                addMessageCommand(cmsg, inv.getName() + " was drawn through the gate to " +
                        ow.getName() + ".", index);
                return false;
            }
        }
        /* Check for Inner Sanctum */
        if (loc.getName().equals("Silver Twilight Lodge") &&
                inv.hasModifier("Investigator Special", "Lodge Membership"))
        {
            locname = "Inner Sanctum";
        }
        if (!evstate.isDefined("!Encounter"))
        {
            List<EventNode> encounters = new ArrayList<EventNode>();
            if (!loc.isStreet())
            {
                //TODO: check for alternate event
                if (loc.getAlternateEvent() != null)
                {

                }

                int choices = 1;
                /* Check for Darrell Simmons power */
                List<ModEffect> modList = inv.searchModifiers("Extra Choices", "Arkham Encounter");
                if (modList.size() > 0)
                {
                    for (ModEffect mod : modList)
                    {
                        choices += mod.getAmountAsInt();
                    }
                }
                /* Check if the encounter was redrawn. If so, limit number of choices to 1 */
                if (evstate.isDefined("RedrawEncounter") &&
                        ((Boolean)evstate.getProperty("RedrawEncounter")))
                {
                    choices = 1;
                }
                if (choices == 1)
                {
                    EventNode en = gameData.drawEventFromLocation(locname, loc.getNeighborhood());
                    encounters.add(en);
                }
                else
                {
                    gameData.drawEventsFromLocation(locname, loc.getNeighborhood(),
                            choices, encounters);
                }
            }

            if (encounters.size() == 0)
            {
                /* No encounter */
                cmsg.addCommand("#Message", inv.getName() + " has no encounter in " +
                		locname);
                return false;
            }
            StringBuffer sb = new StringBuffer();
            for (EventNode enc : encounters)
            {
                sb.append(",");
                sb.append(enc.getName());
            }
            cmsg.addCommand("!Encounter", locname + "," + encounters.size() + sb.toString());
            eventStack.push(evstate);
            return true;
        }
        String encName = (String)evstate.getProperty("!Encounter");
        if (encName.equals("%ALT"))
        {
            /* Alternate Event */

            return false;
        }

        EventNode event = gameData.drawLocationCard(locname, encName);
        if (event == null)
        {
            AHLogger.error("resolveEncounter", "Can't find encounter named " + encName);
            return false;
        }

        /* Check for Redraw Event */
        boolean modsUsed = false;
        if (evstate.isModEffectsSelected())
        {
            modsUsed = true;
            if (!evstate.getModEffects().isEmpty())
            {
                // Redraw used. Redraw one location card
                EventNode en = EventUtils.createEffectNode("Arkham Encounter",
                        inv.getLocationName(), null);
                EventState es = new EventState(en, EventStateType.NEW_EVENT, index);
                es.addProperty("RedrawEncounter", new Boolean(true));
                eventStack.push(es);
                return false;
            }
        }
        List<ModEffect> modList = inv.searchModifiers("Special", "Redraw Location Card");
        GameData.removeUnavailableEffects(inv, modList);
        if (!modList.isEmpty() && !modsUsed)
        {
            EventState es = new EventState(modList, EventStateType.MODIFIER_USAGE, index);
            es.setPrevState(evstate);
            cmsg.addCommand("!RedrawEncounter", locname + "," + encName, index);
            eventStack.push(es);
            return true;
        }

        cmsg.addCommand("#Encounter", locname + "," + encName, index);
        EventState es = new EventState(event, EventStateType.NEW_EVENT, index);
        eventStack.push(es);
        return false;
    }

    /**
     * Resolves the "Other World Encounter" effect.
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveOtherWorldEncounter(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        OtherWorld ow = gameData.getOtherWorld(effect.getDetails());
        /* Other world encounter:
         * 1. Check if !OWEncounter is defined. If defined move to 3
         * 2. Check for Extra Choices power.
         *    Make a list of possible choices and return to player.
         *    Player selects and it will be stored under !OWEncounter
         * 3. Check for Redraw Gate Card modeffect. Prompt if necessary.
         *    a. If chosen, draw another encounter card. Repeat step 2.
         * 4. Finally, execute the encounter.
         */
        if (!evstate.isDefined("!OWEncounter"))
        {
            List<GateCard> gateCards = new ArrayList<GateCard>();
            int choices = 1;
            /* Check for Gloria Goldberg power */
            List<ModEffect> modList = inv.searchModifiers("Extra Choices", "Other World Encounter");
            if (modList.size() > 0)
            {
                for (ModEffect mod : modList)
                {
                    choices += mod.getAmountAsInt();
                }
            }
            /* Check if the encounter was redrawn. If so, limit number of choices to 1 */
            if (evstate.isDefined("RedrawEncounter") &&
                    ((Boolean)evstate.getProperty("RedrawEncounter")))
            {
                choices = 1;
            }
            gameData.drawGateCards(ow, choices, gateCards);
            StringBuffer sb = new StringBuffer();
            for (GateCard card : gateCards)
            {
                sb.append(",");
                sb.append(card.getName());
            }
            cmsg.addCommand("!OWEncounter", ow.getName() + "," + gateCards.size() + sb.toString());
            eventStack.push(evstate);
            return true;
        }
        String cardName = (String)evstate.getProperty("!OWEncounter");

        GateCard card = gameData.getGateCardByName(cardName);
        if (card == null)
        {
            AHLogger.error("resolveEncounter", "Can't find gate card named " + cardName);
            return false;
        }

        /* Check for Redraw Event */
        boolean modsUsed = false;
        if (evstate.isModEffectsSelected())
        {
            modsUsed = true;
            if (!evstate.getModEffects().isEmpty())
            {
                // Redraw used. Redraw one location card
                EventNode en = EventUtils.createEffectNode("Other World Encounter",
                        inv.getLocationName(), null);
                EventState es = new EventState(en, EventStateType.NEW_EVENT,
                        evstate.getPlayer());
                es.addProperty("RedrawEncounter", new Boolean(true));
                eventStack.push(es);
                return false;
            }
        }
        List<ModEffect> modList = inv.searchModifiers("Special", "Redraw Gate Card");
        GameData.removeUnavailableEffects(inv, modList);
        if (!modList.isEmpty() && !modsUsed)
        {
            EventState es = new EventState(modList, EventStateType.MODIFIER_USAGE,
                    evstate.getPlayer());
            es.setPrevState(evstate);
            cmsg.addCommand("!RedrawOWEncounter", ow.getName(), evstate.getPlayer());
            eventStack.push(es);
            return true;
        }

        cmsg.addCommand("#OWEncounter", ow.getName() + "," + card.getName(), evstate.getPlayer());
        EventState es = new EventState(card.getOtherWorldEncounter(ow.getName()),
                EventStateType.NEW_EVENT, evstate.getPlayer());
        eventStack.push(es);
        return false;
    }

    /**
     * Resolves the "Teleport" effect.
     * @param evstate the event state
     * @param cmsg the command message
     * @return true if further action needs to be taken
     */
    private boolean resolveTeleport(EventState evstate, CommandMessage cmsg)
    {
        EffectType effect = evstate.getEffect();
        Investigator inv = gameData.getInvestigator(evstate.getPlayer());
        LocationContainer loc = gameData.getLocationContainer(effect.getDetails());
        if (evstate.isDefined("TeleportFlag"))
        {
            String flag = (String)evstate.getProperty("TeleportFlag");
            if (flag.equals("In Arkham") && !inv.getLocation().isArkhamLocation())
            {
                AHLogger.info("resolveTeleport", inv.getName() + " not teleported due to flag.");
                return false;
            }
        }
        gameData.moveInvestigatorTo(inv, loc);
        cmsg.addCommand("#Teleport", loc.getName());
        addMessageCommand(cmsg, inv.getName() + " moved to " + loc.getName(), evstate.getPlayer());
        return false;
    }

    /**
     * Checks an investigator's sanity and stamina for death conditions.
     * This method is called whenever it is possible that the investigator
     * could have died in a route, eg. after taking damage or paying a spell
     * cost
     * @param player the player's sanity and stamina
     * @return true if the investigator is dead
     */
    private boolean checkForDeath(int player)
    {
        Investigator inv = gameData.getInvestigator(player);
        if (inv.getCurrentSanity() <= 0 && inv.getCurrentStamina() <= 0)
        {
            /* Investigator is devoured */
            EventNode en = EventUtils.createEffectNode("Instant Status", "Devoured", null);
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT, player));
            return true;
        }
        else if (inv.getCurrentStamina() <= 0)
        {
            /* Investigator is unconscious */
            EventNode en = EventUtils.createEffectNode("Instant Status", "Unconscious", null);
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT, player));
            return true;
        }
        else if (inv.getCurrentSanity() <= 0)
        {
            /* Investigator is insane */
            EventNode en = EventUtils.createEffectNode("Instant Status", "Insane", null);
            eventStack.push(new EventState(en, EventStateType.NEW_EVENT, player));
            return true;
        }
        return false;
    }

    /**
     * Gives an investigator a card, and checks for effects on receive
     * @param inv the investigator
     * @param card the investigator card
     */
    private void giveInvestigatorCard(Investigator inv, InvestigatorCard card)
    {
        inv.gainInvestigatorCard(card);
        List<ModEffect> list = card.searchModEffects("Draw Bonus");
        if (list.size() == 0)
        {
            return;
        }
        // in the case of draw bonuses
        String msg = inv.getName() + " gains ";
        List<Pair<String, String>> plist = new ArrayList<Pair<String, String>>();
        for (int i = 0; i < list.size(); i++)
        {
            ModEffect mod = list.get(i);
            plist.add(new Pair<String, String>(mod.getDetails(), mod.getAmount()));
            if (i != 0) msg += ", ";
            msg += mod.getAmount() + " " + mod.getDetails() + " ";
        }
        EventNode en = EventUtils.createTypeSequence("Investigator Gains", plist);
        EventState es = new EventState(en, EventStateType.NEW_EVENT, inv.getIndex());
        es.setMessage(msg + " as a result of drawing " + card.getName());
        eventStack.push(es);
    }

    /**
     * Creates an event signifying the combat damage taken as a result of
     * failing a combat check with a monster
     * @param cmsg the command message
     * @param mon the monster
     * @return an event node representing the damage effects
     */
    private EventNode monsterCombatDamage(CommandMessage cmsg, Monster mon)
    {
        List<EventNode> evlist = new ArrayList<EventNode>();
        int combatDamage = gameData.getModifiedStat(mon, Monster.STAT_COMBAT_DAMAGE);
        if (combatDamage != 0)
        {
            EventNode en = EventUtils.createEffectNode("Damage", "Stamina",
                    String.valueOf(combatDamage));
            evlist.add(en);
        }
        MonsterPower mp = mon.getSpecificPower(MonsterPowerActivation.FAIL_COMBAT);
        if (mp != null)
        {
            EventNode en = EventUtils.createEffectNode(mp.getType(), mp.getDetails(),
                    String.valueOf(mp.getAmount()));
            evlist.add(en);
        }
        if (evlist.size() == 1)
        {
            return evlist.get(0);
        }
        else
        {
            EventNode en = new EventNode();
            en.getSequence().addAll(evlist);
            return en;
        }
    }

    /**
     * Creates an event signifying the sanity damage taken as a result of
     * failing a horror check with a monster
     * @param cmsg the command message
     * @param mon the monster
     * @return an event node representing the damage effects
     */
    private EventNode monsterHorrorDamage(CommandMessage cmsg, Monster mon)
    {
        List<EventNode> evlist = new ArrayList<EventNode>();
        int horrorDamage = gameData.getModifiedStat(mon, Monster.STAT_HORROR_DAMAGE);
        if (horrorDamage != 0)
        {
            EventNode en = EventUtils.createEffectNode("Damage", "Sanity",
                    String.valueOf(horrorDamage));
            evlist.add(en);
        }
        MonsterPower mp = mon.getSpecificPower(MonsterPowerActivation.FAIL_HORROR);
        if (mp != null)
        {
            EventNode en = EventUtils.createEffectNode(mp.getType(), mp.getDetails(),
                    String.valueOf(mp.getAmount()));
            evlist.add(en);
        }
        if (evlist.size() == 1)
        {
            return evlist.get(0);
        }
        else
        {
            EventNode en = new EventNode();
            en.getSequence().addAll(evlist);
            return en;
        }
    }

    /* ---------------- Client / Server code ------------------- */

    /**
     * Called when the server receives a command message
     * @param cmsg the command message
     * @param sender the sender's username
     */
    private void commandReceivedAsServer(CommandMessage cmsg, String sender)
    {
        /*
         * Consult game state to determine which handler to send
         * command to.
         */
        Command command = cmsg.removeFirstCommand();
        serverCommandHandler(command.cmd, command.details, sender);
        advanceGameFlow();
        viewManager.updateDisplay();
        viewManager.updateTurnOrder(gameData.getCurrentPlayer(), activePlayer);
    }

    /**
     * Called when the client receives a command message
     * @param cmsg the command message
     * @param sender the sender's username
     */
    private void commandReceivedAsClient(CommandMessage cmsg, String sender)
    {
        /*
         * Consult game state to determine which handler to send
         * command to.
         */
        clientGameSetupHandler(cmsg, sender);
        viewManager.updateDisplay();
        viewManager.updateTurnOrder(gameData.getCurrentPlayer(), activePlayer);
    }

    /**
     * Sends a UI action as the client. This sends a command message
     * and locks the client's interface until the server responds
     * @param cmd the command
     * @param details the details of the command
     */
    private void sendUIActionAsClient(String cmd, String details)
    {
        synchronized(actionLock)
        {
            suspendAction = true;
            CommandMessage cmsg = new CommandMessage(gameData.getCurrentPlayer());
            cmsg.addCommand(cmd, details);
            client.sendCommandMessage(cmsg);
        }
    }

    /**
     * The server is requested for info
     * @param query the query
     * @return the response to the query
     */
    private String requestForInfo(String query)
    {
        if (query.equals("playerList"))
        {
            StringBuffer sb = new StringBuffer();
            for (String name : players)
            {
                if (sb.length() > 0) sb.append(",");
                sb.append(name);
            }
            return sb.toString();
        }
        else if (query.equals("hostUsername"))
        {
            return hostusername;
        }
        else if (query.equals("scrounge"))
        {
            String cname = gameData.getCommonDeck().revealBottom().getName();
            String uname = gameData.getUniqueDeck().revealBottom().getName();
            String sname = gameData.getSpellDeck().revealBottom().getName();
            return "[Common: " + cname + ", Unique: " + uname + ", Spell: " + sname + "]";
        }

        return null;
    }

    /**
     * The client receives info from the server
     * @param query the query
     * @param response the response to the query
     */
    private void infoReceived(String query, String response)
    {
        if (query.equals("playerJoined"))
        {
            playerJoined(response);
        }
        else if (query.equals("playerLeft"))
        {
            playerLeft(response);
        }
        else if (query.equals("playerList"))
        {
            players.clear();
            String[] tokens = response.split(",");
            for (String player : tokens)
            {
                players.add(player);
            }
        }
        else if (query.equals("scrounge"))
        {
            chatManager.displayMessage("* Scrounge: " + response);
        }
    }

    /**
     * Handler method for when players join
     * @param playerName the player who joined
     */
    private void playerJoined(String playerName)
    {
        chatManager.displayMessage(">> " + playerName + " has joined the game.");
    }

    /**
     * Handler method for when players leave.
     * @param playerName the player who left
     */
    private void playerLeft(String playerName)
    {
        chatManager.displayMessage(">> " + playerName + " has left the game.");
    }

    /* ------------------- Utility Methods --------------------- */

    /**
     * Displays meta-information about a player. Will decide what color to show
     * the message as
     * @param player the player's name
     * @param msg The message to display
     */
    private void displayMetaInfo(String player, String msg)
    {
        if (player.equals(username))
        {
            chatManager.displayMessage("* " + msg);
        }
        else
        {
            chatManager.displayMessage("# " + msg);
        }
    }

    /**
     * Uses a command to update a command message and display it to the host player
     * as well.
     * @param cmsg the command message
     * @param text the text to display
     * @param player the player whose information is being displayed, or -1 to
     * display a system message
     */
    private void addMessageCommand(CommandMessage cmsg, String text, int player)
    {
        if (player == -1)
        {
            chatManager.displayMessage(">> " + text);
            cmsg.addCommand("#Message", ">> " + text);
        }
        else
        {
            displayMetaInfo(gameData.getPlayerName(player), text);
            cmsg.addCommand("#Message", text);
        }
    }

    /**
     * Checks all investigators owned by a player for a certain
     * mod effect
     * @param player the player's username
     * @param type the type to search for
     * @param details the details to search for
     */
    private boolean ownerEffectCheck(String player, String type, String details)
    {
        for (Investigator inv : gameData.getInvestigators())
        {
            if (inv == null)
            {
                continue;
            }
            if (inv.getPlayer().equals(player))
            {
                if (inv.hasModifier(type, details))
                {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Resets the action completed array.
     */
    private void resetActionCompleted()
    {
        Arrays.fill(actionCompleted, false);
    }

    /**
     * Counts the number of successes in a roll
     * @param inv the investigator rolling
     * @param rolltype the roll type
     * @param rollmod the roll modifier, this is only used for Single and Special rolls
     * @param dice the list of dice rolled
     * @param isPassFailType true if the die roll is of the pass/fail rather than
     * the # of successes type. This is only important if the roll type is Special
     * @return the number of successes rolled
     */
    private int countSuccesses(Investigator inv, String rolltype, int rollmod, List<Integer> dice,
            boolean isPassFailType)
    {
        int successes = 0;
        int successMod = 0;
        /* Check for blessings or curses */
        if (inv != null && inv.hasModifier("Dice Effect", "Blessing"))
        {
            successMod = -1;
        }
        else if (inv != null && inv.hasModifier("Dice Effect", "Curse"))
        {
            successMod = 1;
        }

        if (rolltype.equals("Special"))
        {
            /* Blessings do not apply for special rolls */
            if (rollmod == 0 && !isPassFailType)
            {
                successes = dice.get(0);
            }
            else
            {
                if (dice.get(0) >= rollmod)
                {
                    successes = 1;
                }
            }
        }
        else if (rolltype.equals("Single"))
        {
            /* Blessings are applied for single rolls. Success is on
             * a pass/fail basis.
             */
            if (dice.get(0) >= rollmod + successMod)
            {
                successes = 1;
            }
        }
        else
        {
            for (int die : dice)
            {
                if (die >= 5 + successMod)
                {
                    successes++;
                }
            }
        }
        return successes;
    }

    /**
     * Do a simple selection sort on the weapons list. Weapon bonuses and enchant weapon
     * is not taken into account here.
     * @param list the list of weapons
     * @param physRes the physical defence level
     * @param magRes the magical defence level
     */
    private void sortWeapons(List<EffectSource> list, int physRes, int magRes)
    {
        List<EffectSource> tmpList = new ArrayList<EffectSource>();
        while (!list.isEmpty())
        {
            int max = -1;
            int maxindex = 0;
            for (int i = 0; i < list.size(); i++)
            {
                EffectSource effsrc = list.get(i);
                List<ModEffect> modlist = effsrc.searchAlwaysModEffects("Weapon");
                if (modlist.isEmpty() || modlist.size() > 1)
                {
                    AHLogger.warning("sortWeapons", "Invalid weapon: " + effsrc.getName());
                    list.remove(i);
                    i--;
                    continue;
                }
                ModEffect mod = modlist.get(0);
                int val = mod.getAmountAsInt();
                if (mod.getDetails().equals("Physical"))
                {
                    if (physRes == Globals.RESISTANCE)
                    {
                        val = (val + 1) / 2;
                    }
                    else if (physRes == Globals.IMMUNITY)
                    {
                        val = -1;
                    }
                }
                else
                {
                    if (magRes == Globals.RESISTANCE)
                    {
                        val = (val + 1) / 2;
                    }
                    else if (magRes == Globals.IMMUNITY)
                    {
                        val = -1;
                    }
                }
                if (val > max)
                {
                    max = val;
                    maxindex = i;
                }
            }
            tmpList.add(list.remove(maxindex));
        }
    }

    /* ---------------- MESSAGE ENDPOINT METHODS --------------- */

    @Override
    public void displayInfo(String s) {
        chatManager.displayMessage(s);
    }

    @Override
    public void messageReceived(Message m, String sender) {
        if (m instanceof CommandMessage)
        {
            CommandMessage cmsg = (CommandMessage)m;
            AHLogger.info("GameScreen", "Command from " + sender + ": (" + cmsg.getActivePlayer() +
                    ") " + cmsg.toString());
            if (host)
            {
                commandReceivedAsServer(cmsg, sender);
            }
            else
            {
                commandReceivedAsClient(cmsg, sender);
            }
        }
        else if (m instanceof ErrorMessage)
        {
            if (host)
            {
                chatManager.displayMessage(">> Error from " + sender + ": " +
                        ((ErrorMessage)m).getText());
            }
        }
        else if (m instanceof InfoMessage)
        {
            InfoMessage imsg = (InfoMessage)m;
            AHLogger.info("GameScreen", "Info from " + sender + ": " + imsg.getRequest() +
                    "," + imsg.getResponse() + ";");
            if (host)
            {
                if (imsg.getRequest().equals("scrounge"))
                {
                    if (!ownerEffectCheck(sender, "Investigator Special", "Scrounge"))
                    {
                        server.serverTextMessage(">> You do not have the scrounge ability.", sender);
                    }
                    else
                    {
                        server.serverInfoMessage("scrounge", requestForInfo("scrounge"), sender);
                    }
                    return;
                }
                else
                {
                    String info = requestForInfo(imsg.getRequest());
                    if (info != null)
                    {
                        server.serverInfoMessage(imsg.getRequest(), info, sender);
                    }
                }
            }
            else
            {
                infoReceived(imsg.getRequest(), imsg.getResponse());
            }
        }
    }

    @Override
    public void textReceived(String s, String sender) {
        displayInfo(s);
    }

    @Override
    public void newConnection(String username) {
        playerJoined(username);
    }

    @Override
    public void connectionLost(String username) {
        playerLeft(username);
    }

    /* ---------------- WINDOW LISTENER METHODS --------------- */

    @Override
    public void windowActivated(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowClosed(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowClosing(WindowEvent arg0) {
        int response = JOptionPane.showConfirmDialog(this, "Really quit?",
                "Quit", JOptionPane.OK_CANCEL_OPTION);
        if (response == JOptionPane.OK_OPTION)
        {
            if (client != null)
            {
                client.stopClient();
            }
            if (server != null)
            {
                server.stopServer();
            }
            dispose();
        }

    }

    @Override
    public void windowDeactivated(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowDeiconified(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowIconified(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void windowOpened(WindowEvent arg0) {
        // TODO Auto-generated method stub

    }
    /* ---------------- END WINDOW LISTENER METHODS --------------- */
    /* ---------------- MOUSE LISTENER METHODS --------------- */
    @Override
    public void mouseDragged(MouseEvent e) {
        // TODO Auto-generated method stub
        mouseMoved(e);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        // only the map responds to mouse movements right now
        Component jp = viewManager.getLayeredPane();
        Point mp = jp.getMousePosition();
        if (mp != null)
        {
            viewManager.getBoardManager().mouseAt(mp.x, mp.y);
            viewManager.getMouseOverPanel().repaint();
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        //TODO: check if action panel is out
        Point p = viewManager.getMapPanel().getMousePosition(true);
        if (p != null)
        {
            clickedOnMap(e.getButton());
        }
        else
        {
            for (int i = 0; i < gameData.getNumPlayers(); i++)
            {
                p = viewManager.getPlayerPanel(i).getMousePosition(true);
                if (p != null)
                {
                    clickedOnPlayer(i, e.getButton());
                }
            }
        }

    }

    @Override
    public void mouseEntered(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseExited(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mousePressed(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseReleased(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }
    /* ---------------- END MOUSE LISTENER METHODS --------------- */

    @Override
    public void commandReceived(String[] command)
    {
        if (command[0].equals("use"))
        {
            if (command.length < 2)
            {
                chatManager.displayMessage("/use <skill>");
                chatManager.displayMessage("Available skills: scrounge (Ashcan Pete)");
                return;
            }
            if (command[1].equals("scrounge"))
            {
                if (!ownerEffectCheck(username, "Investigator Special", "Scrounge"))
                {
                    chatManager.displayMessage(">> You do not have the scrounge ability.");
                    return;
                }
                if (host)
                {
                    chatManager.displayMessage(requestForInfo("scrounge"));
                }
                else
                {
                    client.sendInfoMessage("scrounge");
                }
            }
        }
        else if (command[0].equals("lookup"))
        {
            if (command.length < 2)
            {
                chatManager.displayMessage("/lookup [card name] : displays info about a card");
                return;
            }
            StringBuffer sb = new StringBuffer();
            for (int i = 1; i < command.length; i++)
            {
                if (i != 1) sb.append(" ");
                sb.append(command[i]);
            }
            InvestigatorCard invcard = gameData.lookupCardDatabase(sb.toString());
            if (invcard == null)
            {
                chatManager.displayMessage("Unable to find \"" + sb.toString() + "\" in database");
            }
            else
            {
                viewManager.getInfoDisplay().displayInvestigatorCard(invcard);
            }
        }
    }

    private static class ReadyChecker
    {
        private List<String> players;
        private HashMap<String, List<String>> readyMap;

        public ReadyChecker (List<String> players)
        {
            this.players = players;
            readyMap = new HashMap<String, List<String>>();
        }

        /**
         * Registers a new ready string to keep track of
         * @param keyword the keyword to use
         */
        public synchronized void register(String keyword)
        {
            if (readyMap.containsKey(keyword))
            {
                return;
            }
            List<String> list = new ArrayList<String>();
            list.addAll(players);
            readyMap.put(keyword, list);
        }

        /**
         * Registers a new ready string to keep track of, and marks
         * the host user as already ready.
         * @param keyword the keyword to use
         * @param hostuser the host username
         */
        public synchronized void register(String keyword, String hostuser)
        {
            if (readyMap.containsKey(keyword))
            {
                return;
            }
            List<String> list = new ArrayList<String>();
            list.addAll(players);
            list.remove(hostuser);
            readyMap.put(keyword, list);
        }

        /**
         * Unregisters a ready string and stops keeping track of it
         * @param keyword the keyword to unregister
         */
        public synchronized void unregister(String keyword)
        {
            readyMap.remove(keyword);
        }

        /**
         * Checks if the keyword has already been defined
         * @param keyword the keyword to check
         * @return true if it has been defined, false otherwise
         */
        public boolean isDefined(String keyword)
        {
            return readyMap.containsKey(keyword);
        }

        /**
         * Called to indicate that a ready with a certain keyword has been
         * received from a player
         * @param keyword the keyword
         * @param name the name of the player who is ready
         * @return true if all players are ready
         */
        public synchronized boolean readyReceived(String keyword, String name)
        {
            if (!isDefined(keyword))
            {
                return false;
            }
            List<String> list = readyMap.get(keyword);
            list.remove(name);
            return list.isEmpty();
        }

        /**
         * Checks if all players are ready for a certain keyword
         * @return the keyword to check
         */
        public synchronized boolean isReady(String keyword)
        {
            if (!isDefined(keyword))
            {
                return false;
            }
            List<String> list = readyMap.get(keyword);
            return list.isEmpty();
        }
    }
}
